From b67750d808fe65e0afc1e7320f4e5a79be10a777 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 12 Jul 2020 20:57:37 +0200 Subject: [PATCH 01/79] Intel --- Moose Development/Moose/Core/Set.lua | 155 ++++- Moose Development/Moose/Ops/Intelligence.lua | 600 +++++++++++++++++++ 2 files changed, 742 insertions(+), 13 deletions(-) create mode 100644 Moose Development/Moose/Ops/Intelligence.lua 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 + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 8c768cc517540ba64515d7a3101fcbcac17d60c7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 12 Jul 2020 22:20:20 +0200 Subject: [PATCH 02/79] Update Intelligence.lua --- Moose Development/Moose/Ops/Intelligence.lua | 24 ++++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 2b0591d0d..ad2e8c76b 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -51,7 +51,7 @@ INTEL = { } --- Detected item info. --- @type INTEL.DetectedItem +-- @type INTEL.Contact -- @field #string groupname Name of the group. -- @field Wrapper.Group#GROUP group The contact group. -- @field #string typename Type name of detected item. @@ -311,7 +311,7 @@ function INTEL:onafterStatus(From, Event, To) if Ncontacts>0 then text="Detected Contacts:" for _,_contact in pairs(self.Contacts) do - local contact=_contact --#INTEL.DetectedItem + local contact=_contact --#INTEL.Contact 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 @@ -448,7 +448,7 @@ function INTEL:CreateDetectedItems(detectedunitset) --- -- Create new contact. - local item={} --#INTEL.DetectedItem + local item={} --#INTEL.Contact item.groupname=groupname item.group=group @@ -473,7 +473,7 @@ function INTEL:CreateDetectedItems(detectedunitset) -- 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 item=self.Contacts[i] --#INTEL.Contact local group=detectedgroupset:FindGroup(item.groupname) @@ -500,7 +500,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #INTEL.DetectedItem Contact Detected contact. +-- @param #INTEL.Contact 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) @@ -511,7 +511,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #INTEL.DetectedItem Contact Detected contact. +-- @param #INTEL.Contact 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) @@ -524,11 +524,11 @@ end --- Create detected items. -- @param #INTEL self -- @param #string groupname Name of the contact group. --- @return #INTEL.DetectedItem +-- @return #INTEL.Contact function INTEL:GetContactByName(groupname) for i,_contact in pairs(self.Contacts) do - local contact=_contact --#INTEL.DetectedItem + local contact=_contact --#INTEL.Contact if contact.groupname==groupname then return contact end @@ -539,18 +539,18 @@ end --- Add a contact to our list. -- @param #INTEL self --- @param #INTEL.DetectedItem Contact The contact to be removed. +-- @param #INTEL.Contact 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. +-- @param #INTEL.Contact Contact The contact to be removed. function INTEL:RemoveContact(Contact) for i,_contact in pairs(self.Contacts) do - local contact=_contact --#INTEL.DetectedItem + local contact=_contact --#INTEL.Contact if contact.groupname==Contact.groupname then table.remove(self.Contacts, i) @@ -562,7 +562,7 @@ end --- Remove a contact from our list. -- @param #INTEL self --- @param #INTEL.DetectedItem Contact The contact to be removed. +-- @param #INTEL.Contact Contact The contact to be removed. -- @return #boolean If true, contact was not detected for at least *dTforget* seconds. function INTEL:CheckContactLost(Contact) From 0611db78b3755c4b24d168b2393de281feacda6b Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 13 Jul 2020 18:31:01 +0200 Subject: [PATCH 03/79] Intel --- Moose Development/Moose/Modules.lua | 1 + Moose Setup/Moose.files | 1 + 2 files changed, 2 insertions(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 48b13adee..da80d374b 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -76,6 +76,7 @@ __Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 994ee660e..39014df5d 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -71,6 +71,7 @@ Ops/FlightGroup.lua Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua +Ops/Intelligence.lua AI/AI_Balancer.lua AI/AI_Air.lua From 56fad29df91e308a99b23a09f1e776f6a2c8edc2 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 14 Jul 2020 17:56:18 +0200 Subject: [PATCH 04/79] ops --- .../Moose/Functional/Warehouse.lua | 9 ++++++- Moose Development/Moose/Ops/AirWing.lua | 25 +++++++++++++++++++ Moose Development/Moose/Ops/Squadron.lua | 1 + 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 297082e22..0bda377f2 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5798,12 +5798,19 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) template.route = {} template.route.routeRelativeTOT=true template.route.points = {} + + local N=asset.grouping or #template.units -- Handle units. - for i=1,#template.units do + for i=1,N do -- Unit template. local unit = template.units[i] + + -- If more units are in the + if i>#template.units then + unit=UTILS.DeepCopy(template.units[1]) + end -- Nillify the unit ID. unit.unitId=nil diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index e12e082c1..e62ac4563 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1463,6 +1463,31 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Create callsign and modex. squad:GetCallsign(asset) squad:GetModex(asset) + + if squad.ngrouping then + local template=asset.template + + local N=math.max(#template.units, squad.ngrouping) + + -- Handle units. + for i=1,N do + + -- Unit template. + local unit = template.units[i] + + -- If grouping is larger than units present, copy first unit. + if i>#template.units then + unit=UTILS.DeepCopy(template.units[1]) + end + + --Remove units if original template contains more than in grouping. + if squad.ngrouping<#template.units and i>#template.units then + unit=nil + end + end + + asset.nunits=squad.ngrouping + end -- Add asset to squadron. squad:AddAsset(asset) diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 5ca14eec4..d1e1bd1c4 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -23,6 +23,7 @@ -- @field #string templatename Name of the template group. -- @field #string aircrafttype Type of the airframe the squadron is using. -- @field Wrapper.Group#GROUP templategroup Template group. +-- @field #number ngrouping User defined number of units in the asset group. -- @field #table assets Squadron assets. -- @field #table missiontypes Capabilities (mission types and performances) of the squadron. -- @field #string livery Livery of the squadron. From b6bc22f991f6fd281e973eec1c89ba0ba99f7a76 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 15 Jul 2020 00:51:27 +0200 Subject: [PATCH 05/79] Squadron Grouping --- Moose Development/Moose/Functional/Warehouse.lua | 9 +-------- Moose Development/Moose/Ops/AirWing.lua | 13 ++++++++----- Moose Development/Moose/Ops/Squadron.lua | 11 +++++++++++ 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0bda377f2..297082e22 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -5798,19 +5798,12 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) template.route = {} template.route.routeRelativeTOT=true template.route.points = {} - - local N=asset.grouping or #template.units -- Handle units. - for i=1,N do + for i=1,#template.units do -- Unit template. local unit = template.units[i] - - -- If more units are in the - if i>#template.units then - unit=UTILS.DeepCopy(template.units[1]) - end -- Nillify the unit ID. unit.unitId=nil diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index e62ac4563..644d58caa 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1455,9 +1455,11 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) if squad then if asset.assignment==assignment then + + local nunits=#asset.template.units -- Debug text. - local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s", squad.name, assignment, asset.unittype, asset.attribute) + local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) self:I(self.lid..text) -- Create callsign and modex. @@ -1476,12 +1478,13 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) local unit = template.units[i] -- If grouping is larger than units present, copy first unit. - if i>#template.units then - unit=UTILS.DeepCopy(template.units[1]) + if i>nunits then + --unit=UTILS.DeepCopy(template.units[1]) + table.insert(template.units, UTILS.DeepCopy(template.units[1])) end - --Remove units if original template contains more than in grouping. - if squad.ngrouping<#template.units and i>#template.units then + -- Remove units if original template contains more than in grouping. + if squad.ngroupingnunits then unit=nil end end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index d1e1bd1c4..a118a878b 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -245,6 +245,17 @@ function SQUADRON:SetRadio(Frequency, Modulation) return self end +--- Set number of units in groups. +-- @param #SQUADRON self +-- @param #nunits Number of units. Must be >=1 and <=4. Default 2. +-- @return #SQUADRON self +function SQUADRON:SetGrouping(nunits) + self.grouping=nunits or 2 + if self.grouping<1 then self.grouping=1 end + if self.grouping>4 then self.grouping=4 end + return self +end + --- Set mission types this squadron is able to perform. -- @param #SQUADRON self -- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. From 81f5184b4303142c936ff1359288eced358cf3f0 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 15 Jul 2020 21:48:11 +0200 Subject: [PATCH 06/79] Added WINCOM;MANDER --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/WingCommander.lua | 755 ++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 757 insertions(+) create mode 100644 Moose Development/Moose/Ops/WingCommander.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index da80d374b..2a3f84f99 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -77,6 +77,7 @@ __Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua new file mode 100644 index 000000000..55e13e0a5 --- /dev/null +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -0,0 +1,755 @@ +--- **Ops** - Commander Air Wing. +-- +-- **Main Features:** +-- +-- * Stuff +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.WingCommander +-- @image OPS_WingCommander.png + + +--- WINGCOMMANDER class. +-- @type WINGCOMMANDER +-- @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 #table airwings Table of airwings. +-- @field #table missionqueue Mission queue. +-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. +-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. +-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. +-- @field #string Defcon Defence condition. +-- @extends Ops.Intelligence#INTEL + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) +-- +-- # The WINGCOMMANDER Concept +-- +-- +-- +-- @field #WINGCOMMANDER +WINGCOMMANDER = { + ClassName = "WINGCOMMANDER", + Debug = nil, + lid = nil, + airwings = {}, + missionqueue = {}, + borderzoneset = nil, + yellowzoneset = nil, + engagezoneset = nil, +} + +--- Contact details. +-- @type WINGCOMMANDER.Contact +-- @field Ops.Auftrag#AUFTRAG mission The assigned mission. +-- @extends Ops.Intelligence#INTEL.DetectedItem + +--- Defence condition. +-- @type WINGCOMMANDER.DEFCON +-- @field #string GREEN No enemy activities detected. +-- @field #string YELLOW Enemy near our border. +-- @field #string RED Enemy within our border. +WINGCOMMANDER.DEFCON = { + GREEN="Green", + YELLOW="Yellow", + RED="Red", +} + +--- WINGCOMMANDER class version. +-- @field #string version +WINGCOMMANDER.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Define A2A and A2G parameters. +-- TODO: Improve airwing selection. Mostly done! +-- DONE: Add/remove spawned flightgroups to detection set. +-- DONE: Borderzones. +-- NOGO: Maybe it's possible to preselect the assets for the mission. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new WINGCOMMANDER object and start the FSM. +-- @param #WINGCOMMANDER self +-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. +-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:New(AgentSet, Coalition) + + AgentSet=AgentSet or SET_GROUP:New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#WINGCOMMANDER + + -- Set some string id for output to DCS.log file. + --self.lid=string.format("WINGCOMMANDER | ") + + self:SetBorderZones() + self:SetYellowZones() + + self:SetThreatLevelRange() + + self.Defcon=WINGCOMMANDER.DEFCON.GREEN + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING. + self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + self:AddTransition("*", "Defcon", "*") -- Cancel mission. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. + -- @function [parent=#WINGCOMMANDER] Start + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. + -- @function [parent=#WINGCOMMANDER] __Start + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers. + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers. + -- @function [parent=#WINGCOMMANDER] __Stop + -- @param #WINGCOMMANDER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#WINGCOMMANDER] Status + -- @param #WINGCOMMANDER self + + --- Triggers the FSM event "SkipperStatus" after a delay. + -- @function [parent=#WINGCOMMANDER] __Status + -- @param #WINGCOMMANDER 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 this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway. +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetAirToAny() + + self:SetFilterCategory({}) + + return self +end + +--- Set this to be an air-to-air dispatcher. +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetAirToAir() + + self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER}) + + return self +end + +--- Set this to be an air-to-ground dispatcher, i.e. engage only ground units +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetAirToGround() + + self:SetFilterCategory({Unit.Category.GROUND_UNIT}) + + return self +end + +--- Set this to be an air-to-sea dispatcher, i.e. engage only naval units. +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetAirToSea() + + self:SetFilterCategory({Unit.Category.SHIP}) + + return self +end + +--- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups. +-- @param #WINGCOMMANDER self +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetAirToSurface() + + self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP}) + + return self +end + +--- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat. +-- Targets with threat level 0 are usually harmless. +-- @param #WINGCOMMANDER self +-- @param #number ThreatLevelMin Min threat level. Default 1. +-- @param #number ThreatLevelMax Max threat level. Default 10. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax) + + self.threatLevelMin=ThreatLevelMin or 1 + self.threatLevelMax=ThreatLevelMax or 10 + + return self +end + +--- Set defence condition. +-- @param #WINGCOMMANDER self +-- @param #string Defcon Defence condition. See @{#WINGCOMMANDER.DEFCON}, e.g. `WINGCOMMANDER.DEFCON.RED`. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetDefcon(Defcon) + + self.Defcon=Defcon + --self:Defcon(Defcon) + + return self +end + + +--- Add an airwing to the wingcommander. +-- @param #WINGCOMMANDER self +-- @param Ops.AirWing#AIRWING Airwing The airwing to add. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddAirwing(Airwing) + + -- This airwing is managed by this wing commander. + Airwing.wingcommander=self + + table.insert(self.airwings, Airwing) + + return self +end + +--- Add mission to mission queue. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddMission(Mission) + + Mission.wingcommander=self + + table.insert(self.missionqueue, Mission) + + return self +end + +--- Remove mission from queue. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:RemoveMission(Mission) + + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==Mission.auftragsnummer then + self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) + table.remove(self.missionqueue, i) + break + end + + end + + return self +end + +--- Set border zone set. +-- @param #WINGCOMMANDER self +-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetBorderZones(BorderZoneSet) + + -- Border zones. + self.borderzoneset=BorderZoneSet or SET_ZONE:New() + + return self +end + +--- Add a zone defining your territory. +-- @param #WINGCOMMANDER self +-- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddBorderZone(BorderZone) + + -- Add a border zone. + self.borderzoneset:AddZone(BorderZone) + + -- Set accept zone. + --self:AddAcceptZone(BorderZone) + + return self +end + +--- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW. +-- @param #WINGCOMMANDER self +-- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:SetYellowZones(YellowZoneSet) + + -- Border zones. + self.yellowzoneset=YellowZoneSet or SET_ZONE:New() + + return self +end + +--- Add a zone defining an area outside your territory that is monitored for enemy activity. +-- @param #WINGCOMMANDER self +-- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory. +-- @return #WINGCOMMANDER self +function WINGCOMMANDER:AddYellowZone(YellowZone) + + -- Add a border zone. + self.yellowzoneset:AddZone(YellowZone) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WINGCOMMANDER:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting Wing Commander") + self:I(self.lid..text) + + -- Start parent INTEL. + self:GetParent(self).onafterStart(self, From, Event, To) + + -- Start attached airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + if airwing:GetState()=="NotReadyYet" then + airwing:Start() + end + end + +end + +--- On after "Status" event. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WINGCOMMANDER:onafterStatus(From, Event, To) + + -- Start parent INTEL. + self:GetParent(self).onafterStatus(self, From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + + -- Clean up missions where the contact was lost. + for _,_contact in pairs(self.ContactsLost) do + local contact=_contact --#WINGCOMMANDER.Contact + + if contact.mission and contact.mission:IsNotOver() then + + local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) + MESSAGE:New(text, 120, "WINGCOMMANDER"):ToAll() + self:I(self.lid..text) + + -- Cancel this mission. + contact.mission:Cancel() + + end + + end + + -- Create missions for all new contacts. + local Nred=0 + local Nyellow=0 + local Nengage=0 + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#WINGCOMMANDER.Contact + local group=contact.group --Wrapper.Group#GROUP + + local inred=self:CheckGroupInBorder(group) + if inred then + Nred=Nred+1 + end + + local inyellow=self:CheckGroupInYellow(group) + if inyellow then + Nyellow=Nyellow+1 + end + + -- Is this a threat? + local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax + + local redalert=true + if self.borderzoneset:Count()>0 then + redalert=inred + end + + if redalert and threat and not contact.mission then + + -- Create a mission based on group category. + local mission=AUFTRAG:NewAUTO(group) + + + -- Add mission to queue. + if mission then + + --TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max. + mission.nassets=1 + + -- Missons are repeated max 3 times on failure. + mission.missionRepeatMax=3 + + -- Set mission contact. + contact.mission=mission + + -- Add mission to queue. + self:AddMission(mission) + end + + end + + end + + -- Set defcon. + -- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones. + if Nred>0 then + self:SetDefcon(WINGCOMMANDER.DEFCON.RED) + elseif Nyellow>0 then + self:SetDefcon(WINGCOMMANDER.DEFCON.YELLOW) + else + self:SetDefcon(WINGCOMMANDER.DEFCON.GREEN) + end + + + -- Check mission queue and assign one PLANNED mission. + self:CheckMissionQueue() + + local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) + self:I(self.lid..text) + + -- Infor about contacts. + if #self.Contacts>0 then + local text="Contacts:" + for i,_contact in pairs(self.Contacts) do + local contact=_contact --#WINGCOMMANDER.Contact + local mtext="N/A" + if contact.mission then + mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) + end + text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext) + end + self:I(self.lid..text) + end + + -- Mission queue. + if #self.missionqueue>0 then + local text="Mission queue:" + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + local target=mission:GetTargetName() or "unknown" + + text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) + end + self:I(self.lid..text) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "MissionAssign" event. Mission is added to the AIRWING mission queue. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.AirWing#AIRWING Airwing The AIRWING. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterMissionAssign(From, Event, To, Airwing, Mission) + + self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) + Airwing:AddMission(Mission) + +end + +--- On after "CancelMission" event. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) + + self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) + + if Mission.status==AUFTRAG.Status.PLANNED then + + -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. + self:RemoveMission(Mission) + + else + + -- Airwing will cancel mission. + if Mission.airwing then + Mission.airwing:MissionCancel(Mission) + end + + end + +end + +--- On before "Defcon" event. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Defcon New defence condition. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onbeforeDefcon(From, Event, To, Defcon) + + local gotit=false + for _,defcon in pairs(WINGCOMMANDER.DEFCON) do + if defcon==Defcon then + gotit=true + end + end + + if not gotit then + self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) + return false + end + + -- Defcon did not change. + if Defcon==self.Defcon then + self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon))) + return false + end + + return true +end + +--- On after "Defcon" event. +-- @param #WINGCOMMANDER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Defcon New defence condition. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function WINGCOMMANDER:onafterDefcon(From, Event, To, Defcon) + self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) + + -- Set new defcon. + self.Defcon=Defcon +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Resources +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check mission queue and assign ONE planned mission. +-- @param #WINGCOMMANDER self +function WINGCOMMANDER:CheckMissionQueue() + + -- TODO: Sort mission queue. wrt what? Threat level? + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- We look for PLANNED missions. + if mission.status==AUFTRAG.Status.PLANNED then + + --- + -- PLANNNED Mission + --- + + local airwing=self:GetAirwingForMission(mission) + + if airwing then + + -- Add mission to airwing. + self:MissionAssign(airwing, mission) + + return + end + + else + + --- + -- Missions NOT in PLANNED state + --- + + end + + end + +end + +--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. +-- @param #WINGCOMMANDER self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return Ops.AirWing#AIRWING The airwing best for this mission. +function WINGCOMMANDER:GetAirwingForMission(Mission) + + -- Table of airwings that can do the mission. + local airwings={} + + -- Loop over all airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + + -- Check if airwing can do this mission. + local can,assets=airwing:CanMission(Mission) + + -- Can it? + if can then + + -- Get coordinate of the target. + local coord=Mission:GetTargetCoordinate() + + if coord then + + -- Distance from airwing to target. + local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) + + -- Add airwing to table of airwings that can. + table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets}) + + end + + end + + end + + -- Can anyone? + if #airwings>0 then + + --- Something like: + -- * Closest airwing that can should be first prio. + -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. + local function score(a) + local d=math.round(a.dist/10) + end + + -- Sort table wrt distance and number of assets. + -- Distances within 10 NM are equal and the airwing with more assets is preferred. + local function sortdist(a,b) + local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6 + local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6 + return adb.nassets) + end + table.sort(airwings, sortdist) + + -- This is the closest airwing to the target. + local airwing=airwings[1].airwing --Ops.AirWing#AIRWING + + return airwing + end + + return nil +end + +--- Check if group is inside our border. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP group The group. +-- @return #boolean If true, group is in any zone. +function WINGCOMMANDER:CheckGroupInBorder(group) + + local inside=self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is near our border (yellow zone). +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP group The group. +-- @return #boolean If true, group is in any zone. +function WINGCOMMANDER:CheckGroupInYellow(group) + + -- Check inside yellow but not inside our border. + local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is inside a zone. +-- @param #WINGCOMMANDER self +-- @param Wrapper.Group#GROUP group The group. +-- @param Core.Set#SET_ZONE zoneset Set of zones. +-- @return #boolean If true, group is in any zone. +function WINGCOMMANDER:CheckGroupInZones(group, zoneset) + + for _,_zone in pairs(zoneset.Set or {}) do + local zone=_zone --Core.Zone#ZONE + + if group:IsPartlyOrCompletelyInZone(zone) then + return true + end + end + + return false +end + +--- Check resources. +-- @param #WINGCOMMANDER self +-- @return #table +function WINGCOMMANDER:CheckResources() + + local capabilities={} + + for _,MissionType in pairs(AUFTRAG.Type) do + capabilities[MissionType]=0 + + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + + -- Get Number of assets that can do this type of missions. + local _,assets=airwing:CanMission(MissionType) + + -- Add up airwing resources. + capabilities[MissionType]=capabilities[MissionType]+#assets + end + + end + + return capabilities +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 39014df5d..4992f7f97 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -72,6 +72,7 @@ Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua +Ops/WingCommander.lua AI/AI_Balancer.lua AI/AI_Air.lua From 9473dc206950f83b41b94717c94a046a02d9ecd1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 16 Jul 2020 01:04:43 +0200 Subject: [PATCH 07/79] Update Intelligence.lua --- Moose Development/Moose/Ops/Intelligence.lua | 47 ++++++++++++++++++-- 1 file changed, 43 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index ad2e8c76b..59eccdb51 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -398,6 +398,45 @@ function INTEL:UpdateIntel() -- Create detected contacts. self:CreateDetectedItems(DetectedSet) + -- Paint a picture of the battlefield. + self:PaintPicture() + +end + + +--- Create detected items. +-- @param #INTEL self +function INTEL:PaintPicture() + + + local contacts={} + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#INTEL.Contact + table.insert(contacts, contact.groupname) + end + + local neighbours={} + for _,_cA in pairs(self.Contacts) do + local cA=_cA --#INTEL.Contact + + neighbours[cA.groupname]={} + + for _,_cB in pairs(self.Contacts) do + local cB=_cB --#INTEL.Contact + + if cA.groupname~=cB.groupname then + + local dist=cA.position:Get2DDistance(cB.position) + + if dist<=10*1000 then + neighbours[cA.groupname]={contactname=cB.groupname, distance=dist} + end + + end + end + end + + end --- Create detected items. @@ -521,10 +560,10 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create detected items. +--- Get a contact by name. -- @param #INTEL self -- @param #string groupname Name of the contact group. --- @return #INTEL.Contact +-- @return #INTEL.Contact The contact. function INTEL:GetContactByName(groupname) for i,_contact in pairs(self.Contacts) do @@ -539,7 +578,7 @@ end --- Add a contact to our list. -- @param #INTEL self --- @param #INTEL.Contact Contact The contact to be removed. +-- @param #INTEL.Contact Contact The contact to be added. function INTEL:AddContact(Contact) table.insert(self.Contacts, Contact) end @@ -560,7 +599,7 @@ function INTEL:RemoveContact(Contact) end ---- Remove a contact from our list. +--- Check if a contact was lost. -- @param #INTEL self -- @param #INTEL.Contact Contact The contact to be removed. -- @return #boolean If true, contact was not detected for at least *dTforget* seconds. From 5cb1036618a7ed0717dea893c7c36ed3d1757c70 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 19 Jul 2020 01:22:49 +0200 Subject: [PATCH 08/79] CHIEF - Added Chief of Staff class. --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/AirWing.lua | 1 - Moose Development/Moose/Ops/Auftrag.lua | 28 +- Moose Development/Moose/Ops/ChiefOfStaff.lua | 698 ++++++++++++++++++ Moose Development/Moose/Ops/Intelligence.lua | 198 ++++- Moose Development/Moose/Ops/Squadron.lua | 6 +- Moose Development/Moose/Ops/WingCommander.lua | 386 +--------- Moose Setup/Moose.files | 1 + 8 files changed, 899 insertions(+), 420 deletions(-) create mode 100644 Moose Development/Moose/Ops/ChiefOfStaff.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 2a3f84f99..7f49bf4ba 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -78,6 +78,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) __Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 644d58caa..56bf7ca4b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1479,7 +1479,6 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- If grouping is larger than units present, copy first unit. if i>nunits then - --unit=UTILS.DeepCopy(template.units[1]) table.insert(template.units, UTILS.DeepCopy(template.units[1])) end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a801c5499..53557a8de 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -33,6 +33,8 @@ -- @field #number Tstop Mission stop time in seconds. -- @field #number duration Mission duration in seconds. -- @field Wrapper.Marker#MARKER marker F10 map marker. +-- @field #boolean markerOn If true, display marker on F10 map with the AUFTRAG status. +-- @field #numberr markerCoaliton Coalition to which the marker is dispayed. -- @field #table DCStask DCS task structure. -- @field #number Ntargets Number of mission targets. -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. @@ -259,6 +261,8 @@ AUFTRAG = { missionFraction = 0.5, enrouteTasks = {}, marker = nil, + markerOn = nil, + markerCoalition = nil, conditionStart = {}, conditionSuccess = {}, conditionFailure = {}, @@ -416,7 +420,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.3.0" +AUFTRAG.version="0.3.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1302,6 +1306,16 @@ function AUFTRAG:SetName(Name) return self end +--- Enable markers, which dispay the mission status on the F10 map. +-- @param #AUFTRAG self +-- @param #number Coalition The coaliton side to which the markers are dispayed. Default is to all. +-- @return #AUFTRAG self +function AUFTRAG:SetEnableMarkers(Coalition) + self.markerOn=true + self.markerCoaliton=Coalition or -1 + return self +end + --- Set weapon type used for the engagement. -- @param #AUFTRAG self -- @param #number WeaponType Weapon type. Default is ENUMS.WeaponFlag.Auto @@ -1821,7 +1835,10 @@ function AUFTRAG:onafterStatus(From, Event, To) end -- Update F10 marker. - self:UpdateMarker() + if self.markerOn then + self:UpdateMarker() + end + end --- Evaluate mission outcome - success or failure. @@ -2863,7 +2880,12 @@ function AUFTRAG:UpdateMarker() -- Get target coordinates. Can be nil! local targetcoord=self:GetTargetCoordinate() - self.marker=MARKER:New(targetcoord, text):ReadOnly():ToAll() + if self.markerCoaliton and self.markerCoaliton>=0 then + self.marker=MARKER:New(targetcoord, text):ReadOnly():ToCoalition(self.markerCoaliton) + else + self.marker=MARKER:New(targetcoord, text):ReadOnly():ToAll() + end + else diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua new file mode 100644 index 000000000..89d98a247 --- /dev/null +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -0,0 +1,698 @@ +--- **Ops** - Chief of Staff. +-- +-- **Main Features:** +-- +-- * Stuff +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Chief +-- @image OPS_Chief.png + + +--- CHIEF class. +-- @type CHIEF +-- @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 #table missionqueue Mission queue. +-- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. +-- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. +-- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. +-- @field #string Defcon Defence condition. +-- @field Ops.WingCommander#WINGCOMMANDER wingcommander Wing commander, commanding airborne forces. +-- @field Ops.Admiral#ADMIRAL admiral Admiral commanding navy forces. +-- @field Ops.General#GENERAL genaral General commanding army forces. +-- @extends Ops.Intelligence#INTEL + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\WingCommander\CHIEF_Main.jpg) +-- +-- # The CHIEF Concept +-- +-- The Chief of staff gathers intel and assigns missions (AUFTRAG) the airforce (WINGCOMMANDER), army (GENERAL) or navy (ADMIRAL). +-- +-- **Note** that currently only assignments to airborne forces (WINGCOMMANDER) are implemented. +-- +-- +-- @field #CHIEF +CHIEF = { + ClassName = "CHIEF", + Debug = nil, + lid = nil, + wingcommander = nil, + admiral = nil, + general = nil, + missionqueue = {}, + borderzoneset = nil, + yellowzoneset = nil, + engagezoneset = nil, +} + +--- Defence condition. +-- @type CHIEF.DEFCON +-- @field #string GREEN No enemy activities detected. +-- @field #string YELLOW Enemy near our border. +-- @field #string RED Enemy within our border. +CHIEF.DEFCON = { + GREEN="Green", + YELLOW="Yellow", + RED="Red", +} + +--- CHIEF class version. +-- @field #string version +CHIEF.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Define A2A and A2G parameters. +-- DONE: Add/remove spawned flightgroups to detection set. +-- DONE: Borderzones. +-- NOGO: Maybe it's possible to preselect the assets for the mission. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new CHIEF object and start the FSM. +-- @param #CHIEF self +-- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. +-- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". +-- @return #CHIEF self +function CHIEF:New(AgentSet, Coalition) + + AgentSet=AgentSet or SET_GROUP:New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#CHIEF + + -- Set some string id for output to DCS.log file. + --self.lid=string.format("CHIEF | ") + + self:SetBorderZones() + self:SetYellowZones() + + self:SetThreatLevelRange() + + self.Defcon=CHIEF.DEFCON.GREEN + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a WINGCOMMANDER. + self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to an ADMIRAL. + self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a GENERAL. + self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + self:AddTransition("*", "Defcon", "*") -- Change defence condition. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CHIEF. Initializes parameters and starts event handlers. + -- @function [parent=#CHIEF] Start + -- @param #CHIEF self + + --- Triggers the FSM event "Start" after a delay. Starts the CHIEF. Initializes parameters and starts event handlers. + -- @function [parent=#CHIEF] __Start + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CHIEF and all its event handlers. + -- @param #CHIEF self + + --- Triggers the FSM event "Stop" after a delay. Stops the CHIEF and all its event handlers. + -- @function [parent=#CHIEF] __Stop + -- @param #CHIEF self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CHIEF] Status + -- @param #CHIEF self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CHIEF] __Status + -- @param #CHIEF 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 this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToAny() + + self:SetFilterCategory({}) + + return self +end + +--- Set this to be an air-to-air dispatcher. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToAir() + + self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER}) + + return self +end + +--- Set this to be an air-to-ground dispatcher, i.e. engage only ground units +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToGround() + + self:SetFilterCategory({Unit.Category.GROUND_UNIT}) + + return self +end + +--- Set this to be an air-to-sea dispatcher, i.e. engage only naval units. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToSea() + + self:SetFilterCategory({Unit.Category.SHIP}) + + return self +end + +--- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups. +-- @param #CHIEF self +-- @return #CHIEF self +function CHIEF:SetAirToSurface() + + self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP}) + + return self +end + +--- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat. +-- Targets with threat level 0 are usually harmless. +-- @param #CHIEF self +-- @param #number ThreatLevelMin Min threat level. Default 1. +-- @param #number ThreatLevelMax Max threat level. Default 10. +-- @return #CHIEF self +function CHIEF:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax) + + self.threatLevelMin=ThreatLevelMin or 1 + self.threatLevelMax=ThreatLevelMax or 10 + + return self +end + +--- Set defence condition. +-- @param #CHIEF self +-- @param #string Defcon Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`. +-- @return #CHIEF self +function CHIEF:SetDefcon(Defcon) + + self.Defcon=Defcon + --self:Defcon(Defcon) + + return self +end + + +--- Set the wing commander for the airforce. +-- @param #CHIEF self +-- @param Ops.WingCommander WingCommander The WINGCOMMANDER object. +-- @return #CHIEF self +function CHIEF:SetWingCommander(WingCommander) + + self.wingcommander=WingCommander + + return self +end + +--- Add mission to mission queue. +-- @param #CHIEF self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. +-- @return #CHIEF self +function CHIEF:AddMission(Mission) + + table.insert(self.missionqueue, Mission) + + return self +end + +--- Remove mission from queue. +-- @param #CHIEF self +-- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. +-- @return #CHIEF self +function CHIEF:RemoveMission(Mission) + + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + if mission.auftragsnummer==Mission.auftragsnummer then + self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) + table.remove(self.missionqueue, i) + break + end + + end + + return self +end + +--- Set border zone set. +-- @param #CHIEF self +-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. +-- @return #CHIEF self +function CHIEF:SetBorderZones(BorderZoneSet) + + -- Border zones. + self.borderzoneset=BorderZoneSet or SET_ZONE:New() + + return self +end + +--- Add a zone defining your territory. +-- @param #CHIEF self +-- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory. +-- @return #CHIEF self +function CHIEF:AddBorderZone(BorderZone) + + -- Add a border zone. + self.borderzoneset:AddZone(BorderZone) + + return self +end + +--- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW. +-- @param #CHIEF self +-- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders. +-- @return #CHIEF self +function CHIEF:SetYellowZones(YellowZoneSet) + + -- Border zones. + self.yellowzoneset=YellowZoneSet or SET_ZONE:New() + + return self +end + +--- Add a zone defining an area outside your territory that is monitored for enemy activity. +-- @param #CHIEF self +-- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory. +-- @return #CHIEF self +function CHIEF:AddYellowZone(YellowZone) + + -- Add a border zone. + self.yellowzoneset:AddZone(YellowZone) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function CHIEF:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting Chief of Staff") + self:I(self.lid..text) + + -- Start parent INTEL. + self:GetParent(self).onafterStart(self, From, Event, To) + + -- Start attached airwings. + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + if airwing:GetState()=="NotReadyYet" then + airwing:Start() + end + end + +end + +--- On after "Status" event. +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function CHIEF:onafterStatus(From, Event, To) + + -- Start parent INTEL. + self:GetParent(self).onafterStatus(self, From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + + -- Clean up missions where the contact was lost. + for _,_contact in pairs(self.ContactsLost) do + local contact=_contact --#CHIEF.Contact + + if contact.mission and contact.mission:IsNotOver() then + + local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) + MESSAGE:New(text, 120, "CHIEF"):ToAll() + self:I(self.lid..text) + + -- Cancel this mission. + contact.mission:Cancel() + + end + + end + + -- Create missions for all new contacts. + local Nred=0 + local Nyellow=0 + local Nengage=0 + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#CHIEF.Contact + local group=contact.group --Wrapper.Group#GROUP + + local inred=self:CheckGroupInBorder(group) + if inred then + Nred=Nred+1 + end + + local inyellow=self:CheckGroupInYellow(group) + if inyellow then + Nyellow=Nyellow+1 + end + + -- Is this a threat? + local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax + + local redalert=true + if self.borderzoneset:Count()>0 then + redalert=inred + end + + if redalert and threat and not contact.mission then + + -- Create a mission based on group category. + local mission=AUFTRAG:NewAUTO(group) + + -- Add mission to queue. + if mission then + + --TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max. + mission.nassets=1 + + -- Missons are repeated max 3 times on failure. + mission.missionRepeatMax=3 + + -- Set mission contact. + contact.mission=mission + + -- Add mission to queue. + self:AddMission(mission) + end + + end + + end + + -- Set defcon. + -- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones. + if Nred>0 then + self:SetDefcon(CHIEF.DEFCON.RED) + elseif Nyellow>0 then + self:SetDefcon(CHIEF.DEFCON.YELLOW) + else + self:SetDefcon(CHIEF.DEFCON.GREEN) + end + + + -- Check mission queue and assign one PLANNED mission. + self:CheckMissionQueue() + + local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) + self:I(self.lid..text) + + -- Infor about contacts. + if #self.Contacts>0 then + local text="Contacts:" + for i,_contact in pairs(self.Contacts) do + local contact=_contact --#CHIEF.Contact + local mtext="N/A" + if contact.mission then + mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) + end + text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext) + end + self:I(self.lid..text) + end + + -- Mission queue. + if #self.missionqueue>0 then + local text="Mission queue:" + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + local target=mission:GetTargetName() or "unknown" + + text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) + end + self:I(self.lid..text) + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "MissionAssign" event. Mission is added to the AIRWING mission queue. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.AirWing#AIRWING Airwing The AIRWING. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function CHIEF:onafterMissionAssign(From, Event, To, Airwing, Mission) + + self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) + Airwing:AddMission(Mission) + +end + +--- On after "CancelMission" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function CHIEF:onafterCancelMission(From, Event, To, Mission) + + self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) + + if Mission.status==AUFTRAG.Status.PLANNED then + + -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. + self:RemoveMission(Mission) + + else + + -- Airwing will cancel mission. + if Mission.airwing then + Mission.airwing:MissionCancel(Mission) + end + + end + +end + +--- On before "Defcon" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Defcon New defence condition. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function CHIEF:onbeforeDefcon(From, Event, To, Defcon) + + local gotit=false + for _,defcon in pairs(CHIEF.DEFCON) do + if defcon==Defcon then + gotit=true + end + end + + if not gotit then + self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) + return false + end + + -- Defcon did not change. + if Defcon==self.Defcon then + self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon))) + return false + end + + return true +end + +--- On after "Defcon" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Defcon New defence condition. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +function CHIEF:onafterDefcon(From, Event, To, Defcon) + self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) + + -- Set new defcon. + self.Defcon=Defcon +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Resources +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check mission queue and assign ONE planned mission. +-- @param #CHIEF self +function CHIEF:CheckMissionQueue() + + -- TODO: Sort mission queue. wrt what? Threat level? + + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + -- We look for PLANNED missions. + if mission.status==AUFTRAG.Status.PLANNED then + + --- + -- PLANNNED Mission + --- + + local airwing=self:GetAirwingForMission(mission) + + if airwing then + + -- Add mission to airwing. + self:MissionAssign(airwing, mission) + + return + end + + else + + --- + -- Missions NOT in PLANNED state + --- + + end + + end + +end + +--- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. +-- @param #CHIEF self +-- @param Ops.Auftrag#AUFTRAG Mission The mission. +-- @return Ops.AirWing#AIRWING The airwing best for this mission. +function CHIEF:GetAirwingForMission(Mission) + + if self.wingcommander then + return self.wingcommander:GetAirwingForMission(Mission) + end + + return nil +end + +--- Check if group is inside our border. +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP group The group. +-- @return #boolean If true, group is in any zone. +function CHIEF:CheckGroupInBorder(group) + + local inside=self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is near our border (yellow zone). +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP group The group. +-- @return #boolean If true, group is in any zone. +function CHIEF:CheckGroupInYellow(group) + + -- Check inside yellow but not inside our border. + local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset) + + return inside +end + +--- Check if group is inside a zone. +-- @param #CHIEF self +-- @param Wrapper.Group#GROUP group The group. +-- @param Core.Set#SET_ZONE zoneset Set of zones. +-- @return #boolean If true, group is in any zone. +function CHIEF:CheckGroupInZones(group, zoneset) + + for _,_zone in pairs(zoneset.Set or {}) do + local zone=_zone --Core.Zone#ZONE + + if group:IsPartlyOrCompletelyInZone(zone) then + return true + end + end + + return false +end + +--- Check resources. +-- @param #CHIEF self +-- @return #table +function CHIEF:CheckResources() + + local capabilities={} + + for _,MissionType in pairs(AUFTRAG.Type) do + capabilities[MissionType]=0 + + for _,_airwing in pairs(self.airwings) do + local airwing=_airwing --Ops.AirWing#AIRWING + + -- Get Number of assets that can do this type of missions. + local _,assets=airwing:CanMission(MissionType) + + -- Add up airwing resources. + capabilities[MissionType]=capabilities[MissionType]+#assets + end + + end + + return capabilities +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 59eccdb51..a70b7c402 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -24,6 +24,8 @@ -- @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 #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 @@ -48,6 +50,8 @@ INTEL = { Contacts = {}, ContactsLost = {}, ContactsUnknown = {}, + Clusters = {}, + clustercounter = 1, } --- Detected item info. @@ -65,6 +69,15 @@ INTEL = { -- @field #number speed Last known speed. -- @field #number markerID F10 map marker ID. +--- Cluster info. +-- @type INTEL.Cluster +-- @field #number size Number of groups in the cluster. +-- @field #table Contacts Table of contacts in the cluster. +-- @field #number threatlevelMax Max threat level of cluster. +-- @field #number threatlevelSum Sum of threat levels. +-- @field Wrapper.Marker#MARKER marker F10 marker. + + --- INTEL class version. -- @field #string version INTEL.version="0.0.3" @@ -404,40 +417,8 @@ function INTEL:UpdateIntel() end ---- Create detected items. --- @param #INTEL self -function INTEL:PaintPicture() - local contacts={} - for _,_contact in pairs(self.Contacts) do - local contact=_contact --#INTEL.Contact - table.insert(contacts, contact.groupname) - end - - local neighbours={} - for _,_cA in pairs(self.Contacts) do - local cA=_cA --#INTEL.Contact - - neighbours[cA.groupname]={} - - for _,_cB in pairs(self.Contacts) do - local cB=_cB --#INTEL.Contact - - if cA.groupname~=cB.groupname then - - local dist=cA.position:Get2DDistance(cB.position) - - if dist<=10*1000 then - neighbours[cA.groupname]={contactname=cB.groupname, distance=dist} - end - - end - end - end - - -end --- Create detected items. -- @param #INTEL self @@ -517,7 +498,7 @@ function INTEL:CreateDetectedItems(detectedunitset) 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 + if self:_CheckContactLost(item) then -- Trigger LostContact event. This also adds the contact to the self.ContactsLost table. self:LostContact(item) @@ -603,7 +584,7 @@ end -- @param #INTEL self -- @param #INTEL.Contact Contact The contact to be removed. -- @return #boolean If true, contact was not detected for at least *dTforget* seconds. -function INTEL:CheckContactLost(Contact) +function INTEL:_CheckContactLost(Contact) -- Group dead? if Contact.group==nil or not Contact.group:IsAlive() then @@ -634,6 +615,155 @@ function INTEL:CheckContactLost(Contact) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Cluster Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create detected items. +-- @param #INTEL self +function INTEL:PaintPicture() + + local contacts={} + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#INTEL.Contact + if not self:CheckContactInClusters(contact) then + table.insert(contacts, contact.groupname) + end + end + + local contacts={} + for i,_cA in pairs(self.Contacts) do + local cA=_cA --#INTEL.Contact + + if not self:CheckContactInClusters(cA) then + + local n=0 + local neighbours={} + for _,_cB in pairs(self.Contacts) do + local cB=_cB --#INTEL.Contact + + if cA.groupname~=cB.groupname and not self:CheckContactInClusters(cB) then + + local dist=cA.position:Get2DDistance(cB.position) + + if dist<=10*1000 then + n=n+1 + table.insert(neighbours, {name=cB.groupname, distance=dist}) + end + + end + end + + table.insert(contacts, {name=cA.groupname, n=n, neighbours=neighbours}) + + end + end + + -- Sort contacts with respect to number of neighbours. + local function sort(a,b) + return a.n>b.n + end + table.sort(contacts, sort) + + + --- Remove a contact. + local function removecontact(contactname) + for i,contact in pairs(contacts) do + if contact.name==contactname then + table.remove(contacts, i) + end + end + end + + --[[ + env.info("FF before removing neighbours") + for _,contact in pairs(contacts) do + env.info(string.format("Group %s has %d neighbours", contact.name, contact.n)) + end + ]] + + + local function checkcontact(contact) + + for _,c in pairs(contacts) do + if c.name==contact.name then + return true + end + end + return false + end + + for _,contact in pairs(UTILS.DeepCopy(contacts)) do + + if checkcontact(contact) then + + local cluster={} --#INTEL.Cluster + cluster.index=self.clustercounter + + cluster.groups={} + table.insert(cluster.groups, contact.name) + + cluster.Contacts={} + table.insert(cluster.groups, self:GetContactByName(contact.name)) + + local Contact=self:GetContactByName(contact.name) + cluster.coordinate=Contact.position + + cluster.size=1 + for _,neighbour in pairs(contact.neighbours) do + + removecontact(neighbour.name) + + table.insert(cluster.groups, neighbour.name) + cluster.size=cluster.size+1 + + table.insert(cluster.groups, self:GetContactByName(neighbour.name)) + end + + local text=string.format("Cluster #%d. Size %d", cluster.index, cluster.size) + cluster.maker=MARKER:New(cluster.coordinate, text):ToAll() + + -- Add cluster. + table.insert(self.Clusters, cluster) + + -- Increase couter. + self.clustercounter=self.clustercounter+1 + + end + end + + --[[ + table.sort(contacts, sort) + + env.info("FF after removing neighbours") + for i,cluster in pairs(clusters) do + env.info(string.format("Cluster %d has %d groups", i, cluster.n)) + end + ]] + +end + +--- Check if contact is in any known cluster. +-- @param #INTEL self +-- @param #INTEL.Contact contact The contact. +-- @return #boolean If true, contact is in clusters +function INTEL:CheckContactInClusters(contact) + + for _,_cluster in pairs(self.Clusters) do + local cluster=_cluster --#INTEL.Cluster + + for _,_contact in pairs(cluster.Contacts) do + local Contact=_contact --#INTEL.Contact + + if Contact.groupname==contact.groupname then + return true + end + end + end + + return false +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index a118a878b..99ac9c9b1 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -250,9 +250,9 @@ end -- @param #nunits Number of units. Must be >=1 and <=4. Default 2. -- @return #SQUADRON self function SQUADRON:SetGrouping(nunits) - self.grouping=nunits or 2 - if self.grouping<1 then self.grouping=1 end - if self.grouping>4 then self.grouping=4 end + self.ngrouping=nunits or 2 + if self.ngrouping<1 then self.ngrouping=1 end + if self.ngrouping>4 then self.ngrouping=4 end return self end diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index 55e13e0a5..b6ada11ae 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -16,13 +16,9 @@ -- @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 #table airwings Table of airwings. +-- @field #table airwings Table of airwings which are commanded. -- @field #table missionqueue Mission queue. --- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. --- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. --- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. --- @field #string Defcon Defence condition. --- @extends Ops.Intelligence#INTEL +-- @extends Core.Fsm#FSM --- Be surprised! -- @@ -31,7 +27,8 @@ -- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) -- -- # The WINGCOMMANDER Concept --- +-- +-- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned AUFTRAG (mission). -- -- -- @field #WINGCOMMANDER @@ -41,25 +38,6 @@ WINGCOMMANDER = { lid = nil, airwings = {}, missionqueue = {}, - borderzoneset = nil, - yellowzoneset = nil, - engagezoneset = nil, -} - ---- Contact details. --- @type WINGCOMMANDER.Contact --- @field Ops.Auftrag#AUFTRAG mission The assigned mission. --- @extends Ops.Intelligence#INTEL.DetectedItem - ---- Defence condition. --- @type WINGCOMMANDER.DEFCON --- @field #string GREEN No enemy activities detected. --- @field #string YELLOW Enemy near our border. --- @field #string RED Enemy within our border. -WINGCOMMANDER.DEFCON = { - GREEN="Green", - YELLOW="Yellow", - RED="Red", } --- WINGCOMMANDER class version. @@ -82,25 +60,11 @@ WINGCOMMANDER.version="0.1.0" --- Create a new WINGCOMMANDER object and start the FSM. -- @param #WINGCOMMANDER self --- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. --- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". -- @return #WINGCOMMANDER self -function WINGCOMMANDER:New(AgentSet, Coalition) - - AgentSet=AgentSet or SET_GROUP:New() +function WINGCOMMANDER:New() -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#WINGCOMMANDER - - -- Set some string id for output to DCS.log file. - --self.lid=string.format("WINGCOMMANDER | ") - - self:SetBorderZones() - self:SetYellowZones() - - self:SetThreatLevelRange() - - self.Defcon=WINGCOMMANDER.DEFCON.GREEN + local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER -- Add FSM transitions. -- From State --> Event --> To State @@ -133,7 +97,7 @@ function WINGCOMMANDER:New(AgentSet, Coalition) -- @function [parent=#WINGCOMMANDER] Status -- @param #WINGCOMMANDER self - --- Triggers the FSM event "SkipperStatus" after a delay. + --- Triggers the FSM event "Status" after a delay. -- @function [parent=#WINGCOMMANDER] __Status -- @param #WINGCOMMANDER self -- @param #number delay Delay in seconds. @@ -156,83 +120,6 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetAirToAny() - - self:SetFilterCategory({}) - - return self -end - ---- Set this to be an air-to-air dispatcher. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetAirToAir() - - self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER}) - - return self -end - ---- Set this to be an air-to-ground dispatcher, i.e. engage only ground units --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetAirToGround() - - self:SetFilterCategory({Unit.Category.GROUND_UNIT}) - - return self -end - ---- Set this to be an air-to-sea dispatcher, i.e. engage only naval units. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetAirToSea() - - self:SetFilterCategory({Unit.Category.SHIP}) - - return self -end - ---- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetAirToSurface() - - self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP}) - - return self -end - ---- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat. --- Targets with threat level 0 are usually harmless. --- @param #WINGCOMMANDER self --- @param #number ThreatLevelMin Min threat level. Default 1. --- @param #number ThreatLevelMax Max threat level. Default 10. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax) - - self.threatLevelMin=ThreatLevelMin or 1 - self.threatLevelMax=ThreatLevelMax or 10 - - return self -end - ---- Set defence condition. --- @param #WINGCOMMANDER self --- @param #string Defcon Defence condition. See @{#WINGCOMMANDER.DEFCON}, e.g. `WINGCOMMANDER.DEFCON.RED`. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetDefcon(Defcon) - - self.Defcon=Defcon - --self:Defcon(Defcon) - - return self -end - - --- Add an airwing to the wingcommander. -- @param #WINGCOMMANDER self -- @param Ops.AirWing#AIRWING Airwing The airwing to add. @@ -280,57 +167,6 @@ function WINGCOMMANDER:RemoveMission(Mission) return self end ---- Set border zone set. --- @param #WINGCOMMANDER self --- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetBorderZones(BorderZoneSet) - - -- Border zones. - self.borderzoneset=BorderZoneSet or SET_ZONE:New() - - return self -end - ---- Add a zone defining your territory. --- @param #WINGCOMMANDER self --- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddBorderZone(BorderZone) - - -- Add a border zone. - self.borderzoneset:AddZone(BorderZone) - - -- Set accept zone. - --self:AddAcceptZone(BorderZone) - - return self -end - ---- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW. --- @param #WINGCOMMANDER self --- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:SetYellowZones(YellowZoneSet) - - -- Border zones. - self.yellowzoneset=YellowZoneSet or SET_ZONE:New() - - return self -end - ---- Add a zone defining an area outside your territory that is monitored for enemy activity. --- @param #WINGCOMMANDER self --- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddYellowZone(YellowZone) - - -- Add a border zone. - self.yellowzoneset:AddZone(YellowZone) - - return self -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -374,107 +210,9 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - - -- Clean up missions where the contact was lost. - for _,_contact in pairs(self.ContactsLost) do - local contact=_contact --#WINGCOMMANDER.Contact - - if contact.mission and contact.mission:IsNotOver() then - - local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) - MESSAGE:New(text, 120, "WINGCOMMANDER"):ToAll() - self:I(self.lid..text) - - -- Cancel this mission. - contact.mission:Cancel() - - end - - end - - -- Create missions for all new contacts. - local Nred=0 - local Nyellow=0 - local Nengage=0 - for _,_contact in pairs(self.Contacts) do - local contact=_contact --#WINGCOMMANDER.Contact - local group=contact.group --Wrapper.Group#GROUP - - local inred=self:CheckGroupInBorder(group) - if inred then - Nred=Nred+1 - end - - local inyellow=self:CheckGroupInYellow(group) - if inyellow then - Nyellow=Nyellow+1 - end - - -- Is this a threat? - local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax - - local redalert=true - if self.borderzoneset:Count()>0 then - redalert=inred - end - - if redalert and threat and not contact.mission then - - -- Create a mission based on group category. - local mission=AUFTRAG:NewAUTO(group) - - - -- Add mission to queue. - if mission then - - --TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max. - mission.nassets=1 - - -- Missons are repeated max 3 times on failure. - mission.missionRepeatMax=3 - - -- Set mission contact. - contact.mission=mission - - -- Add mission to queue. - self:AddMission(mission) - end - - end - - end - - -- Set defcon. - -- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones. - if Nred>0 then - self:SetDefcon(WINGCOMMANDER.DEFCON.RED) - elseif Nyellow>0 then - self:SetDefcon(WINGCOMMANDER.DEFCON.YELLOW) - else - self:SetDefcon(WINGCOMMANDER.DEFCON.GREEN) - end - - -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() - local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) - self:I(self.lid..text) - - -- Infor about contacts. - if #self.Contacts>0 then - local text="Contacts:" - for i,_contact in pairs(self.Contacts) do - local contact=_contact --#WINGCOMMANDER.Contact - local mtext="N/A" - if contact.mission then - mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) - end - text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext) - end - self:I(self.lid..text) - end - -- Mission queue. if #self.missionqueue>0 then local text="Mission queue:" @@ -534,50 +272,6 @@ function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) end ---- On before "Defcon" event. --- @param #WINGCOMMANDER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Defcon New defence condition. --- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onbeforeDefcon(From, Event, To, Defcon) - - local gotit=false - for _,defcon in pairs(WINGCOMMANDER.DEFCON) do - if defcon==Defcon then - gotit=true - end - end - - if not gotit then - self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) - return false - end - - -- Defcon did not change. - if Defcon==self.Defcon then - self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon))) - return false - end - - return true -end - ---- On after "Defcon" event. --- @param #WINGCOMMANDER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Defcon New defence condition. --- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterDefcon(From, Event, To, Defcon) - self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) - - -- Set new defcon. - self.Defcon=Defcon -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -684,72 +378,6 @@ function WINGCOMMANDER:GetAirwingForMission(Mission) return nil end ---- Check if group is inside our border. --- @param #WINGCOMMANDER self --- @param Wrapper.Group#GROUP group The group. --- @return #boolean If true, group is in any zone. -function WINGCOMMANDER:CheckGroupInBorder(group) - - local inside=self:CheckGroupInZones(group, self.borderzoneset) - - return inside -end - ---- Check if group is near our border (yellow zone). --- @param #WINGCOMMANDER self --- @param Wrapper.Group#GROUP group The group. --- @return #boolean If true, group is in any zone. -function WINGCOMMANDER:CheckGroupInYellow(group) - - -- Check inside yellow but not inside our border. - local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset) - - return inside -end - ---- Check if group is inside a zone. --- @param #WINGCOMMANDER self --- @param Wrapper.Group#GROUP group The group. --- @param Core.Set#SET_ZONE zoneset Set of zones. --- @return #boolean If true, group is in any zone. -function WINGCOMMANDER:CheckGroupInZones(group, zoneset) - - for _,_zone in pairs(zoneset.Set or {}) do - local zone=_zone --Core.Zone#ZONE - - if group:IsPartlyOrCompletelyInZone(zone) then - return true - end - end - - return false -end - ---- Check resources. --- @param #WINGCOMMANDER self --- @return #table -function WINGCOMMANDER:CheckResources() - - local capabilities={} - - for _,MissionType in pairs(AUFTRAG.Type) do - capabilities[MissionType]=0 - - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - - -- Get Number of assets that can do this type of missions. - local _,assets=airwing:CanMission(MissionType) - - -- Add up airwing resources. - capabilities[MissionType]=capabilities[MissionType]+#assets - end - - end - - return capabilities -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 4992f7f97..36f5f604a 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -73,6 +73,7 @@ Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua Ops/WingCommander.lua +Ops/ChiefOfStaff.lua AI/AI_Balancer.lua AI/AI_Air.lua From 5fa75bf6b9ae62ba7ad7338548c938bee603c14d Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 19 Jul 2020 18:31:09 +0200 Subject: [PATCH 09/79] INTEL Improved clusters --- Moose Development/Moose/Ops/Auftrag.lua | 5 +- Moose Development/Moose/Ops/ChiefOfStaff.lua | 13 +- Moose Development/Moose/Ops/Intelligence.lua | 264 +++++++++++++++---- 3 files changed, 226 insertions(+), 56 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 53557a8de..d469c417c 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1810,7 +1810,7 @@ function AUFTRAG:onafterStatus(From, Event, To) local commander=self.wingcommander and tostring(self.wingcommander.coalition) or "N/A" -- Info message. - self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) + self:T(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) -- Check for error. if fsmstate~=self.status then @@ -1822,7 +1822,7 @@ function AUFTRAG:onafterStatus(From, Event, To) local groupdata=_groupdata --#AUFTRAG.GroupData text=text..string.format("\n- %s: status mission=%s opsgroup=%s", groupname, groupdata.status, groupdata.opsgroup and groupdata.opsgroup:GetState() or "N/A") end - self:I(self.lid..text) + self:T(self.lid..text) local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false @@ -2885,7 +2885,6 @@ function AUFTRAG:UpdateMarker() else self.marker=MARKER:New(targetcoord, text):ReadOnly():ToAll() end - else diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index 89d98a247..eb7cd5a29 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -488,17 +488,20 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "MissionAssign" event. Mission is added to the AIRWING mission queue. +--- On after "AssignMissionAssignAirforce" event. -- @param #CHIEF self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.AirWing#AIRWING Airwing The AIRWING. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function CHIEF:onafterMissionAssign(From, Event, To, Airwing, Mission) +function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) - self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) - Airwing:AddMission(Mission) + if self.wingcommander then + self:I(self.lid..string.format("Assigning mission %s (%s) to WINGCOMMANDER", Mission.name, Mission.type)) + self.wingcommander:AddMission(Mission) + else + self:E(self.lid..string.format("Mission cannot be assigned as no WINGCOMMANDER is defined.")) + end end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index a70b7c402..1af4d6f8e 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -281,7 +281,6 @@ end --- 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. @@ -297,7 +296,6 @@ 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. @@ -619,15 +617,55 @@ end -- Cluster Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create detected items. +--- Paint picture of the battle field. -- @param #INTEL self function INTEL:PaintPicture() + -- First remove all lost contacts from clusters. + for _,_contact in pairs(self.ContactsLost) do + local contact=_contact --#INTEL.Contact + local cluster=self:GetClustersOfContact(contact) + if cluster then + self:RemoveContactFromCluster(contact, cluster) + end + end + + + for _,_contact in pairs(self.Contacts) do + local contact=_contact --#INTEL.Contact + + if not self:CheckContactInClusters(contact) then + + local cluster=self:IsContactPartOfAnyClusters(contact) + + if cluster then + self:AddContactToCluster(contact, cluster) + else + + local newcluster=self:CreateCluster(contact.position) + self:AddContactToCluster(contact, newcluster) + end + + end + + end + + if false then + local contacts={} for _,_contact in pairs(self.Contacts) do local contact=_contact --#INTEL.Contact if not self:CheckContactInClusters(contact) then - table.insert(contacts, contact.groupname) + + -- Check if contact is part of known clusters. + local cluster=self:IsContactPartOfAnyCluster(contact) + + if cluster then + self:AddContactToCluster(contact, cluster) + else + table.insert(contacts, contact.groupname) + end + end end @@ -674,17 +712,9 @@ function INTEL:PaintPicture() end end end - - --[[ - env.info("FF before removing neighbours") - for _,contact in pairs(contacts) do - env.info(string.format("Group %s has %d neighbours", contact.name, contact.n)) - end - ]] - - - local function checkcontact(contact) - + + --- Check if a contact is in the list. + local function checkcontact(contact) for _,c in pairs(contacts) do if c.name==contact.name then return true @@ -696,53 +726,126 @@ function INTEL:PaintPicture() for _,contact in pairs(UTILS.DeepCopy(contacts)) do if checkcontact(contact) then - - local cluster={} --#INTEL.Cluster - cluster.index=self.clustercounter - - cluster.groups={} - table.insert(cluster.groups, contact.name) - - cluster.Contacts={} - table.insert(cluster.groups, self:GetContactByName(contact.name)) - + local Contact=self:GetContactByName(contact.name) - cluster.coordinate=Contact.position + + local cluster=self:CreateCluster(Contact.position) + + self:AddContactToCluster(Contact, cluster) - cluster.size=1 for _,neighbour in pairs(contact.neighbours) do - + + -- Remove contact from table as this is now part of a cluster. removecontact(neighbour.name) - table.insert(cluster.groups, neighbour.name) - cluster.size=cluster.size+1 - - table.insert(cluster.groups, self:GetContactByName(neighbour.name)) + local Neighbour=self:GetContactByName(neighbour.name) + + self:AddContactToCluster(Neighbour, cluster) + end - local text=string.format("Cluster #%d. Size %d", cluster.index, cluster.size) - cluster.maker=MARKER:New(cluster.coordinate, text):ToAll() - - -- Add cluster. - table.insert(self.Clusters, cluster) - - -- Increase couter. - self.clustercounter=self.clustercounter+1 end end - - --[[ - table.sort(contacts, sort) - - env.info("FF after removing neighbours") - for i,cluster in pairs(clusters) do - env.info(string.format("Cluster %d has %d groups", i, cluster.n)) + + end -- if false then + + -- Update F10 marker text if cluster has changed. + for _,cluster in pairs(self.Clusters) do + -- Update F10 marker. + self:UpdateClusterMarker(cluster) end - ]] end +--- Create a new cluster. +-- @param #INTEL self +-- @param Core.Point#COORDINATE coordinate The coordinate of the cluster. +-- @return #INTEL.Cluster cluster The cluster. +function INTEL:CreateCluster(coordinate) + + -- Create new cluster + local cluster={} --#INTEL.Cluster + + cluster.index=self.clustercounter + cluster.coordinate=coordinate + cluster.threatlevelSum=0 + cluster.threatlevelMax=0 + cluster.size=0 + cluster.Contacts={} + + -- Add cluster. + table.insert(self.Clusters, cluster) + + -- Increase counter. + self.clustercounter=self.clustercounter+1 + + return cluster +end + +--- Add a contact to the cluster. +-- @param #INTEL self +-- @param #INTEL.Contact contact The contact. +-- @param #INTEL.Cluster cluster The cluster. +function INTEL:AddContactToCluster(contact, cluster) + + if contact and cluster then + + -- Add neighbour to cluster contacts. + table.insert(cluster.Contacts, contact) + + cluster.threatlevelSum=cluster.threatlevelSum+contact.threatlevel + + cluster.size=cluster.size+1 + end + +end + +--- Remove a contact from a cluster. +-- @param #INTEL self +-- @param #INTEL.Contact contact The contact. +-- @param #INTEL.Cluster cluster The cluster. +function INTEL:RemoveContactFromCluster(contact, cluster) + + if contact and cluster then + + for i,_contact in pairs(cluster.Contacts) do + local Contact=_contact --#INTEL.Contact + + if Contact.groupname==contact.groupname then + + cluster.threatlevelSum=cluster.threatlevelSum-contact.threatlevel + cluster.size=cluster.size-1 + + table.remove(cluster.Contacts, i) + + return + end + + end + + end + +end + +--- Calculate cluster threatlevel sum. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Sum of all threat levels of all groups in the cluster. +function INTEL:CalcClusterThreatlevelSum(cluster) + + local threatlevel=0 + + for _,_contact in pairs(cluster.Contacts) do + local contact=_contact --#INTEL.Contact + + threatlevel=threatlevel+contact.threatlevel + + end + + return threatlevel +end + --- Check if contact is in any known cluster. -- @param #INTEL self -- @param #INTEL.Contact contact The contact. @@ -764,6 +867,71 @@ function INTEL:CheckContactInClusters(contact) return false end +--- Check if contact is close to any contact of known clusters. +-- @param #INTEL self +-- @param #INTEL.Contact contact The contact. +-- @return #INTEL.Cluster The cluster this contact is part of or nil otherwise. +function INTEL:IsContactPartOfAnyClusters(contact) + + for _,_cluster in pairs(self.Clusters) do + local cluster=_cluster --#INTEL.Cluster + + for _,_contact in pairs(cluster.Contacts) do + local Contact=_contact --#INTEL.Contact + + local dist=Contact.position:Get2DDistance(contact.position) + + if dist<10*1000 then + return cluster + end + + end + + end + + return nil +end +--- Check if contact is in any known cluster. +-- @param #INTEL self +-- @param #INTEL.Contact contact The contact. +-- @return #INTEL.Cluster The cluster this contact belongs to or nil. +function INTEL:GetClustersOfContact(contact) + + for _,_cluster in pairs(self.Clusters) do + local cluster=_cluster --#INTEL.Cluster + + for _,_contact in pairs(cluster.Contacts) do + local Contact=_contact --#INTEL.Contact + + if Contact.groupname==contact.groupname then + return cluster + end + end + end + + return nil +end + +--- Update cluster F10 marker. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster. +function INTEL:UpdateClusterMarker(cluster) + + -- Create a marker. + local text=string.format("Cluster #%d. Size %d, TLsum=%d", cluster.index, cluster.size, cluster.threatlevelSum) + + if not cluster.marker then + cluster.marker=MARKER:New(cluster.coordinate, text):ToAll() + else + + if cluster.marker.text~=text then + cluster.marker:UpdateText(text) + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From ce1121a4c2520ad01a9c64573b0150895d9da0d1 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 20 Jul 2020 01:20:28 +0200 Subject: [PATCH 10/79] INTEL Clusters --- Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/Intelligence.lua | 299 +++++++++++-------- 2 files changed, 179 insertions(+), 122 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index d469c417c..2501b6693 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1822,7 +1822,7 @@ function AUFTRAG:onafterStatus(From, Event, To) local groupdata=_groupdata --#AUFTRAG.GroupData text=text..string.format("\n- %s: status mission=%s opsgroup=%s", groupname, groupdata.status, groupdata.opsgroup and groupdata.opsgroup:GetState() or "N/A") end - self:T(self.lid..text) + self:I(self.lid..text) local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 1af4d6f8e..8d62a180a 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -71,10 +71,13 @@ INTEL = { --- Cluster info. -- @type INTEL.Cluster +-- @field #number index Cluster index. -- @field #number size Number of groups in the cluster. -- @field #table Contacts Table of contacts in the cluster. -- @field #number threatlevelMax Max threat level of cluster. -- @field #number threatlevelSum Sum of threat levels. +-- @field #number threatlevelAve Average of threat levels. +-- @field Core.Point#COORDINATE coordinate Coordinate of the cluster. -- @field Wrapper.Marker#MARKER marker F10 marker. @@ -624,7 +627,7 @@ function INTEL:PaintPicture() -- First remove all lost contacts from clusters. for _,_contact in pairs(self.ContactsLost) do local contact=_contact --#INTEL.Contact - local cluster=self:GetClustersOfContact(contact) + local cluster=self:GetClusterOfContact(contact) if cluster then self:RemoveContactFromCluster(contact, cluster) end @@ -634,7 +637,41 @@ function INTEL:PaintPicture() for _,_contact in pairs(self.Contacts) do local contact=_contact --#INTEL.Contact - if not self:CheckContactInClusters(contact) then + -- Check if this contact is in any cluster. + local isincluster=self:CheckContactInClusters(contact) + + -- Get the current cluster (if any) this contact belongs to. + local currentcluster=self:GetClusterOfContact(contact) + + if currentcluster then + + --- + -- Contact is currently part of a cluster. + --- + + -- Check if the contact is still connected to the cluster. + local isconnected=self:IsContactConnectedToCluster(contact, currentcluster) + + if not isconnected then + + local cluster=self:IsContactPartOfAnyClusters(contact) + + if cluster then + self:AddContactToCluster(contact, cluster) + else + + local newcluster=self:CreateCluster(contact.position) + self:AddContactToCluster(contact, newcluster) + end + + end + + + else + + --- + -- Contact is not in any cluster yet. + --- local cluster=self:IsContactPartOfAnyClusters(contact) @@ -645,115 +682,22 @@ function INTEL:PaintPicture() local newcluster=self:CreateCluster(contact.position) self:AddContactToCluster(contact, newcluster) end - - end - - end - - if false then - - local contacts={} - for _,_contact in pairs(self.Contacts) do - local contact=_contact --#INTEL.Contact - if not self:CheckContactInClusters(contact) then - - -- Check if contact is part of known clusters. - local cluster=self:IsContactPartOfAnyCluster(contact) - - if cluster then - self:AddContactToCluster(contact, cluster) - else - table.insert(contacts, contact.groupname) - end end - end - - local contacts={} - for i,_cA in pairs(self.Contacts) do - local cA=_cA --#INTEL.Contact - if not self:CheckContactInClusters(cA) then - - local n=0 - local neighbours={} - for _,_cB in pairs(self.Contacts) do - local cB=_cB --#INTEL.Contact - - if cA.groupname~=cB.groupname and not self:CheckContactInClusters(cB) then - - local dist=cA.position:Get2DDistance(cB.position) - - if dist<=10*1000 then - n=n+1 - table.insert(neighbours, {name=cB.groupname, distance=dist}) - end - - end - end - - table.insert(contacts, {name=cA.groupname, n=n, neighbours=neighbours}) - - end - end - - -- Sort contacts with respect to number of neighbours. - local function sort(a,b) - return a.n>b.n - end - table.sort(contacts, sort) - - - --- Remove a contact. - local function removecontact(contactname) - for i,contact in pairs(contacts) do - if contact.name==contactname then - table.remove(contacts, i) - end - end - end - - --- Check if a contact is in the list. - local function checkcontact(contact) - for _,c in pairs(contacts) do - if c.name==contact.name then - return true - end - end - return false end - for _,contact in pairs(UTILS.DeepCopy(contacts)) do - - if checkcontact(contact) then - - local Contact=self:GetContactByName(contact.name) - - local cluster=self:CreateCluster(Contact.position) - - self:AddContactToCluster(Contact, cluster) - - for _,neighbour in pairs(contact.neighbours) do - - -- Remove contact from table as this is now part of a cluster. - removecontact(neighbour.name) - - local Neighbour=self:GetContactByName(neighbour.name) - - self:AddContactToCluster(Neighbour, cluster) - end - - - end - end - - end -- if false then -- Update F10 marker text if cluster has changed. - for _,cluster in pairs(self.Clusters) do + for _,_cluster in pairs(self.Clusters) do + local cluster=_cluster --#INTEL.Cluster + + local coordinate=self:GetClusterCoordinate(cluster) + + -- Update F10 marker. - self:UpdateClusterMarker(cluster) + self:UpdateClusterMarker(cluster, coordinate) end end @@ -828,7 +772,7 @@ function INTEL:RemoveContactFromCluster(contact, cluster) end ---- Calculate cluster threatlevel sum. +--- Calculate cluster threat level sum. -- @param #INTEL self -- @param #INTEL.Cluster cluster The cluster of contacts. -- @return #number Sum of all threat levels of all groups in the cluster. @@ -846,6 +790,39 @@ function INTEL:CalcClusterThreatlevelSum(cluster) return threatlevel end +--- Calculate cluster threat level average. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Average of all threat levels of all groups in the cluster. +function INTEL:CalcClusterThreatlevelAverage(cluster) + + local threatlevel=self:CalcClusterThreatlevelSum(cluster) + threatlevel=threatlevel/cluster.size + + return threatlevel +end + +--- Calculate max cluster threat level. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Max threat levels of all groups in the cluster. +function INTEL:CalcClusterThreatlevelMax(cluster) + + local threatlevel=0 + + for _,_contact in pairs(cluster.Contacts) do + local contact=_contact --#INTEL.Contact + + if contact.threatlevel>threatlevel then + threatlevel=contact.threatlevel + end + + end + + return threatlevel +end + + --- Check if contact is in any known cluster. -- @param #INTEL self -- @param #INTEL.Contact contact The contact. @@ -867,6 +844,31 @@ function INTEL:CheckContactInClusters(contact) return false end +--- Check if contact is close to any other contact this cluster. +-- @param #INTEL self +-- @param #INTEL.Contact contact The contact. +-- @param #INTEL.Cluster cluster The cluster the check. +-- @return #boolean If true, contact is connected to this cluster. +function INTEL:IsContactConnectedToCluster(contact, cluster) + + for _,_contact in pairs(cluster.Contacts) do + local Contact=_contact --#INTEL.Contact + + if Contact.groupname~=contact.groupname then + + local dist=Contact.position:Get2DDistance(contact.position) + + if dist<10*1000 then + return true + end + + end + + end + + return false +end + --- Check if contact is close to any contact of known clusters. -- @param #INTEL self -- @param #INTEL.Contact contact The contact. @@ -875,27 +877,20 @@ function INTEL:IsContactPartOfAnyClusters(contact) for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster - - for _,_contact in pairs(cluster.Contacts) do - local Contact=_contact --#INTEL.Contact - - local dist=Contact.position:Get2DDistance(contact.position) - - if dist<10*1000 then - return cluster - end - - end - + + if self:IsContactConnectedToCluster(contact, cluster) then + return cluster + end end return nil end ---- Check if contact is in any known cluster. + +--- Get the cluster this contact belongs to (if any). -- @param #INTEL self -- @param #INTEL.Contact contact The contact. -- @return #INTEL.Cluster The cluster this contact belongs to or nil. -function INTEL:GetClustersOfContact(contact) +function INTEL:GetClusterOfContact(contact) for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster @@ -912,20 +907,82 @@ function INTEL:GetClustersOfContact(contact) return nil end +--- Get the coordinate of a cluster. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster. +-- @return Core.Point#COORDINATE The coordinate of this cluster. +function INTEL:GetClusterCoordinate(cluster) + + -- Init. + local x=0 ; local y=0 ; local z=0 ; local n=0 + + for _,_contact in pairs(cluster.Contacts) do + local contact=_contact --#INTEL.Contact + + x=x+contact.position.x + y=y+contact.position.y + y=y+contact.position.z + n=n+1 + + end + + -- Average. + x=x/n ; y=y/n ; z=z/n + + -- Create coordinate. + local coordinate=COORDINATE:New(x, y, z) + + return coordinate +end + +--- Get the coordinate of a cluster. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster. +-- @param Core.Point#COORDINATE coordinate (Optional) Coordinate of the new cluster. Default is to calculate the current coordinate. +function INTEL:CheckClusterCoordinateChanged(cluster, coordinate) + + coordinate=coordinate or self:GetClusterCoordinate(cluster) + + local dist=cluster.coordinate:Get2DDistance(coordinate) + + if dist>1000 then + return true + else + return false + end + +end + + --- Update cluster F10 marker. -- @param #INTEL self --- @param #INTEL.Cluster cluster The cluster. -function INTEL:UpdateClusterMarker(cluster) +-- @param #INTEL.Cluster cluster The cluster. +-- @param Core.Point#COORDINATE newcoordinate Updated cluster positon. +function INTEL:UpdateClusterMarker(cluster, newcoordinate) -- Create a marker. local text=string.format("Cluster #%d. Size %d, TLsum=%d", cluster.index, cluster.size, cluster.threatlevelSum) - if not cluster.marker then + if not cluster.marker then cluster.marker=MARKER:New(cluster.coordinate, text):ToAll() else + local refresh=false + if cluster.marker.text~=text then - cluster.marker:UpdateText(text) + --cluster.marker:UpdateText(text) + cluster.marker.text=text + refresh=true + end + + if newcoordinate then + cluster.coordinate=newcoordinate + cluster.marker.coordinate=cluster.coordinate + refresh=true + end + + if refresh then + cluster.marker:Refresh() end end From d9b7cc18f3f49cbcb22bad8364ba616e4a2ab851 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Jul 2020 01:19:03 +0200 Subject: [PATCH 11/79] A* --- Moose Development/Moose/Core/Astar.lua | 439 +++++++++++++++++++ Moose Development/Moose/Core/Point.lua | 9 +- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/Airboss.lua | 2 +- Moose Development/Moose/Ops/Intelligence.lua | 9 +- Moose Development/Moose/Ops/NavyGroup.lua | 2 +- 6 files changed, 453 insertions(+), 9 deletions(-) create mode 100644 Moose Development/Moose/Core/Astar.lua diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua new file mode 100644 index 000000000..20db867ba --- /dev/null +++ b/Moose Development/Moose/Core/Astar.lua @@ -0,0 +1,439 @@ +--- **Core** - Pathfinding. +-- +-- **Main Features:** +-- +-- * Stuff +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Core.Astar +-- @image CORE_Atar.png + + +--- ASTAR class. +-- @type ASTAR +-- @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 #table nodes Table of nodes. +-- @field #ASTAR.Node startNode Start node. +-- @field #ASTAR.Node endNode End node. +-- @field Core.Point#COORDINATE startCoord Start coordinate. +-- @field Core.Point#COORDINATE endCoord End coordinate. +-- @field #func CheckNodeValid Function to check if a node is valid. +-- @extends Core.Base#BASE + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\WingCommander\ASTAR_Main.jpg) +-- +-- # The ASTAR Concept +-- +-- Pathfinding algorithm. +-- +-- +-- +-- @field #ASTAR +ASTAR = { + ClassName = "ASTAR", + Debug = nil, + lid = nil, + nodes = {}, + CheckNodeValid = nil, +} + +--- Defence condition. +-- @type ASTAR.Node +-- @field Core.Point#COORDINATE coordinate Coordinate of the node. +-- @field #number surfacetype Surface type. + +--- ASTAR infinity +-- @field #string INF +ASTAR.INF=1/0 + +--- ASTAR class version. +-- @field #string version +ASTAR.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new ASTAR object and start the FSM. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, BASE:New()) --#ASTAR + + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set coordinate from where to start. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate Start coordinate. +-- @return #ASTAR self +function ASTAR:SetStartCoordinate(Coordinate) + + self.startCoord=Coordinate + + return self +end + +--- Set coordinate from where to go. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate end coordinate. +-- @return #ASTAR self +function ASTAR:SetEndCoordinate(Coordinate) + + self.endCoord=Coordinate + + return self +end + +--- Add a node. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate The coordinate. +-- @return #ASTAR.Node The node. +function ASTAR:GetNodeFromCoordinate(Coordinate) + + local node={} --#ASTAR.Node + + node.coordinate=Coordinate + node.surfacetype=Coordinate:GetSurfaceType() + + return node +end + + +--- Add a node. +-- @param #ASTAR self +-- @param #ASTAR.Node Node The node to be added to the nodes table. +-- @return #ASTAR self +function ASTAR:AddNode(Node) + + table.insert(self.nodes, Node) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Find the closest node from a given coordinate. +-- @param #ASTAR.Node nodeA +-- @param #ASTAR.Node nodeB +function ASTAR.LoS(nodeA, nodeB) + + local los=nodeA.coordinate:IsLOS(nodeB.coordinate, 0.5) + + if los then + local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) + + local Ap=nodeA.coordinate:Translate(100, heading+90) + local Bp=nodeA.coordinate:Translate(100, heading+90) + + los=Ap:IsLOS(Bp, 0.5) + + if los then + + local Am=nodeA.coordinate:Translate(100, heading-90) + local Bm=nodeA.coordinate:Translate(100, heading-90) + + los=Am:IsLOS(Bm, 0.5) + end + + end + + return los +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Find the closest node from a given coordinate. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate. +-- @return #ASTAR.Node Cloest node to the coordinate. +function ASTAR:CreateGrid() + + local Dx=20000 + local Dz=10000 + local delta=2000 + + local angle=self.startCoord:HeadingTo(self.endCoord) + local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz + + local co=COORDINATE:New(0, 0, 0) + + local do1=co:Get2DDistance(self.startCoord) + local ho1=co:HeadingTo(self.startCoord) + + local xmin=-Dx + local zmin=-Dz + + local nz=dist/delta+1 + local nx=2*Dx/delta+1 + + env.info(string.format("FF building grid with nx=%d ny=%d total=%d nodes. Angle=%d, dist=%d meters", nx, nz, nx*nz, angle, dist)) + for i=1,nx do + + local x=xmin+delta*(i-1) + + for j=1,nz do + + local z=zmin+delta*(j-1) + + local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle) + + local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true) + + if c:IsSurfaceTypeWater() then + + --c:MarkToAll(string.format("i=%d, j=%d", i, j)) + + local node=self:GetNodeFromCoordinate(c) + self:AddNode(node) + + end + + end + end + env.info("FF Done building grid!") + +end + +--- Find the closest node from a given coordinate. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate. +-- @return #ASTAR.Node Cloest node to the coordinate. +function ASTAR:FindClosestNode(Coordinate) + + local distMin=math.huge + local closeNode=nil + + for _,_node in pairs(self.nodes) do + local node=_node --#ASTAR.Node + + local dist=node.coordinate:Get2DDistance(Coordinate) + + if dist 0 do + + local current = self:lowest_f_score ( openset, f_score ) + + if current == goal then + local path = self:unwind_path ( {}, came_from, goal ) + table.insert(path, goal) + return path + end + + self:remove_node( openset, current ) + table.insert ( closedset, current ) + + local neighbors = self:neighbor_nodes( current, nodes ) + + for _, neighbor in ipairs ( neighbors ) do + + if self:not_in ( closedset, neighbor ) then + + local tentative_g_score = g_score [ current ] + self:dist_between ( current, neighbor ) + + if self:not_in ( openset, neighbor ) or tentative_g_score < g_score [ neighbor ] then + + came_from [ neighbor ] = current + g_score [ neighbor ] = tentative_g_score + f_score [ neighbor ] = g_score [ neighbor ] + self:heuristic_cost_estimate ( neighbor, goal ) + + if self:not_in ( openset, neighbor ) then + table.insert ( openset, neighbor ) + end + + end + end + end + end + + return nil -- no valid path +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 0559ed660..4b7b9a7ef 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1921,15 +1921,18 @@ do -- COORDINATE --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self -- @param #COORDINATE ToCoordinate + -- @param #number OFfset Height offset in meters. Default 2 m. -- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false. - function COORDINATE:IsLOS( ToCoordinate ) + function COORDINATE:IsLOS( ToCoordinate, Offset ) + + Offset=Offset or 2 -- Measurement of visibility should not be from the ground, so Adding a hypotethical 2 meters to each Coordinate. local FromVec3 = self:GetVec3() - FromVec3.y = FromVec3.y + 2 + FromVec3.y = FromVec3.y + Offset local ToVec3 = ToCoordinate:GetVec3() - ToVec3.y = ToVec3.y + 2 + ToVec3.y = ToVec3.y + Offset local IsLOS = land.isVisible( FromVec3, ToVec3 ) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 7f49bf4ba..f4a6ab8b6 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -26,6 +26,7 @@ __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) +__Moose.Include( 'Scripts/Moose/Core/Astar.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' ) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index aa34cee89..16de9bf86 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -2464,7 +2464,7 @@ function AIRBOSS:AddRecoveryWindow(starttime, stoptime, case, holdingoffset, tur local Tstart=UTILS.ClockToSeconds(starttime) -- Set stop time. - local Tstop=UTILS.ClockToSeconds(stoptime or Tstart+90*60) + local Tstop=stoptime and UTILS.ClockToSeconds(stoptime) or Tstart+90*60 -- Consistancy check for timing. if Tstart>Tstop then diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 8d62a180a..4773499a7 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -66,8 +66,10 @@ INTEL = { -- @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. +-- @field #number speed Last known speed in m/s. +-- @field #boolean isship +-- @field #boolean ishelo +-- @field #boolean isgrund --- Cluster info. -- @type INTEL.Cluster @@ -921,7 +923,7 @@ function INTEL:GetClusterCoordinate(cluster) x=x+contact.position.x y=y+contact.position.y - y=y+contact.position.z + z=z+contact.position.z n=n+1 end @@ -970,7 +972,6 @@ function INTEL:UpdateClusterMarker(cluster, newcoordinate) local refresh=false if cluster.marker.text~=text then - --cluster.marker:UpdateText(text) cluster.marker.text=text refresh=true end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index bada43c13..d6fe5816f 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -242,7 +242,7 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) local Tstart=UTILS.ClockToSeconds(starttime) -- Set stop time. - local Tstop=UTILS.ClockToSeconds(stoptime or Tstart+90*60) + local Tstop=stoptime and UTILS.ClockToSeconds(stoptime) or Tstart+90*60 -- Consistancy check for timing. if Tstart>Tstop then From af023c19941527966a58eaff2345c14cb12b4cfa Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 21 Jul 2020 17:53:59 +0200 Subject: [PATCH 12/79] Waypoints --- Moose Development/Moose/Core/Astar.lua | 22 +- Moose Development/Moose/Ops/AirWing.lua | 2 +- Moose Development/Moose/Ops/ArmyGroup.lua | 899 ++++++++++++++++++++ Moose Development/Moose/Ops/FlightGroup.lua | 37 +- Moose Development/Moose/Ops/NavyGroup.lua | 35 +- Moose Development/Moose/Ops/OpsGroup.lua | 253 ++++-- Moose Development/Moose/Ops/Squadron.lua | 4 +- Moose Development/Moose/Utilities/Utils.lua | 2 +- 8 files changed, 1111 insertions(+), 143 deletions(-) create mode 100644 Moose Development/Moose/Ops/ArmyGroup.lua diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 20db867ba..3ee069619 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -1,4 +1,4 @@ ---- **Core** - Pathfinding. +--- **Core** - A* Pathfinding. -- -- **Main Features:** -- @@ -272,7 +272,7 @@ end -- @param #ASTAR.Node nodeA Node A. -- @param #ASTAR.Node nodeB Node B. -- @return #number Distance between nodes in meters. -function ASTAR:dist_between ( nodeA, nodeB ) +function ASTAR:DistNodes ( nodeA, nodeB ) return nodeA.coordinate:Get2DDistance(nodeB.coordinate) end @@ -281,8 +281,8 @@ end -- @param #ASTAR.Node nodeA Node A. -- @param #ASTAR.Node nodeB Node B. -- @return #number Distance between nodes in meters. -function ASTAR:heuristic_cost_estimate( nodeA, nodeB ) - return self:dist_between(nodeA, nodeB) +function ASTAR:HeuristicCost( nodeA, nodeB ) + return self:DistNodes(nodeA, nodeB) end --- Function @@ -318,7 +318,7 @@ end --- Function -- @param #ASTAR self -function ASTAR:neighbor_nodes ( theNode, nodes ) +function ASTAR:neighbor_nodes(theNode, nodes) local neighbors = {} for _, node in ipairs ( nodes ) do @@ -360,11 +360,11 @@ end --- Function -- @param #ASTAR self -function ASTAR:unwind_path ( flat_path, map, current_node ) +function ASTAR:UnwindPath( flat_path, map, current_node ) if map [ current_node ] then table.insert ( flat_path, 1, map [ current_node ] ) - return self:unwind_path ( flat_path, map, map [ current_node ] ) + return self:UnwindPath ( flat_path, map, map [ current_node ] ) else return flat_path end @@ -393,14 +393,14 @@ function ASTAR:GetPath() g_score [ start ] = 0 - f_score [ start ] = g_score [ start ] + self:heuristic_cost_estimate ( start, goal ) + f_score [ start ] = g_score [ start ] + self:HeuristicCost ( start, goal ) while #openset > 0 do local current = self:lowest_f_score ( openset, f_score ) if current == goal then - local path = self:unwind_path ( {}, came_from, goal ) + local path = self:UnwindPath ( {}, came_from, goal ) table.insert(path, goal) return path end @@ -414,13 +414,13 @@ function ASTAR:GetPath() if self:not_in ( closedset, neighbor ) then - local tentative_g_score = g_score [ current ] + self:dist_between ( current, neighbor ) + local tentative_g_score = g_score [ current ] + self:DistNodes ( current, neighbor ) if self:not_in ( openset, neighbor ) or tentative_g_score < g_score [ neighbor ] then came_from [ neighbor ] = current g_score [ neighbor ] = tentative_g_score - f_score [ neighbor ] = g_score [ neighbor ] + self:heuristic_cost_estimate ( neighbor, goal ) + f_score [ neighbor ] = g_score [ neighbor ] + self:HeuristicCost ( neighbor, goal ) if self:not_in ( openset, neighbor ) then table.insert ( openset, neighbor ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 56bf7ca4b..b894e7621 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -66,7 +66,7 @@ -- At this point the airwing does not have any assets (aircraft). In order to add these, one needs to first define SQUADRONS. -- -- VFA151=SQUADRON:New("F-14 Group", 8, "VFA-151 (Vigilantes)") --- VFA151:AddMissonCapability({AUFTRAG.Type.PATROL, AUFTRAG.Type.INTERCEPT}) +-- VFA151:AddMissionCapability({AUFTRAG.Type.PATROL, AUFTRAG.Type.INTERCEPT}) -- -- airwing:AddSquadron(VFA151) -- diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua new file mode 100644 index 000000000..0533311fc --- /dev/null +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -0,0 +1,899 @@ +--- **Ops** - Enhanced Ground Group. +-- +-- **Main Features:** +-- +-- * Dynamically add and remove waypoints. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.ArmyGroup +-- @image OPS_ArmyGroup.png + + +--- ARMYGROUP class. +-- @type ARMYGROUP +-- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. +-- @extends Ops.OpsGroup#OPSGROUP + +--- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson +-- +-- === +-- +-- ![Banner Image](..\Presentations\ARMYGROUP\NavyGroup_Main.jpg) +-- +-- # The ARMYGROUP Concept +-- +-- This class enhances naval groups. +-- +-- @field #ARMYGROUP +ARMYGROUP = { + ClassName = "ARMYGROUP", +} + +--- Navy group element. +-- @type ARMYGROUP.Element +-- @field #string name Name of the element, i.e. the unit. +-- @field #string typename Type name. + +--- NavyGroup version. +-- @field #string version +ARMYGROUP.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new ARMYGROUP class object. +-- @param #ARMYGROUP self +-- @param #string GroupName Name of the group. +-- @return #ARMYGROUP self +function ARMYGROUP:New(GroupName) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, OPSGROUP:New(GroupName)) -- #ARMYGROUP + + -- Set some string id for output to DCS.log file. + self.lid=string.format("ARMYGROUP %s | ", self.groupname) + + -- Defaults + self:SetDefaultROE() + self:SetDetection() + self:SetPatrolAdInfinitum(true) + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("*", "FullStop", "Holding") -- Hold position. + + self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. + self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Stop". Stops the ARMYGROUP and all its event handlers. + -- @param #ARMYGROUP self + + --- Triggers the FSM event "Stop" after a delay. Stops the ARMYGROUP and all its event handlers. + -- @function [parent=#ARMYGROUP] __Stop + -- @param #ARMYGROUP self + -- @param #number delay Delay in seconds. + + -- TODO: Add pseudo functions. + + + -- Init waypoints. + self:InitWaypoints() + + -- Initialize the group. + self:_InitGroup() + + -- Debug trace. + if false then + self.Debug=true + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + BASE:TraceLevel(1) + end + + -- Handle events: + self:HandleEvent(EVENTS.Birth, self.OnEventBirth) + self:HandleEvent(EVENTS.Dead, self.OnEventDead) + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) + + -- Start the status monitoring. + self:__CheckZone(-1) + self:__Status(-2) + self:__QueueUpdate(-3) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. +-- @param #ARMYGROUP self +-- @param #boolean switch If true or nil, patrol until the end of time. If false, go along the waypoints once and stop. +-- @return #ARMYGROUP self +function ARMYGROUP:SetPatrolAdInfinitum(switch) + if switch==false then + self.adinfinitum=false + else + self.adinfinitum=true + end + return self +end + +--- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. +-- @param #ARMYGROUP self +-- @param #number Speed Speed in knots. Default 70% of max speed. +-- @return #ARMYGROUP self +function ARMYGROUP:SetSpeedCruise(Speed) + + self.speedCruise=Speed and UTILS.KnotsToKmph(Speed) or self.speedmax*0.7 + + return self +end + + +--- Add a *scheduled* task. +-- @param #ARMYGROUP self +-- @param Core.Point#COORDINATE Coordinate Coordinate of the target. +-- @param #number Radius Radius in meters. Default 100 m. +-- @param #number Nshots Number of shots to fire. Default 3. +-- @param #number WeaponType Type of weapon. Default auto. +-- @param #string Clock Time when to start the attack. +-- @param #number Prio Priority of the task. +function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Clock, Prio) + + local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) + + self:AddTask(DCStask, Clock, nil, Prio) + +end + +--- Add a *scheduled* task. +-- @param #ARMYGROUP self +-- @param Wrapper.Group#GROUP TargetGroup Target group. +-- @param #number WeaponExpend How much weapons does are used. +-- @param #number WeaponType Type of weapon. Default auto. +-- @param #string Clock Time when to start the attack. +-- @param #number Prio Priority of the task. +function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clock, Prio) + + local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack) + + self:AddTask(DCStask, Clock, nil, Prio) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +---- Update status. +-- @param #ARMYGROUP self +function ARMYGROUP:onbeforeStatus(From, Event, To) + + if self:IsDead() then + self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) + return false + elseif self:IsStopped() then + self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) + return false + end + + return true +end + +--- Update status. +-- @param #ARMYGROUP self +function ARMYGROUP:onafterStatus(From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + --- + -- Detection + --- + + -- Check if group has detected any units. + if self.detectionOn then + self:_CheckDetectedUnits() + end + + if self:IsAlive() and not self:IsDead() then + + -- Current heading and position of the carrier. + local hdg=self:GetHeading() + local pos=self:GetCoordinate() + local speed=self.group:GetVelocityKNOTS() + + + -- Get number of tasks and missions. + local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() + local nMissions=self:CountRemainingMissison() + + -- Info text. + local text=string.format("State %s: Wp=%d/%d Speed=%.1f Heading=%03d Tasks=%d Missions=%d", + fsmstate, self.currentwp, #self.waypoints, speed, hdg, nTaskTot, nMissions) + self:I(self.lid..text) + + else + + -- Info text. + local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) + self:I(self.lid..text) + + end + + + --- + -- Tasks + --- + + -- Task queue. + if #self.taskqueue>0 and self.verbose>1 then + local text=string.format("Tasks #%d", #self.taskqueue) + for i,_task in pairs(self.taskqueue) do + local task=_task --Ops.OpsGroup#OPSGROUP.Task + local name=task.description + local taskid=task.dcstask.id or "unknown" + local status=task.status + local clock=UTILS.SecondsToClock(task.time, true) + local eta=task.time-timer.getAbsTime() + local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A" + local duration=-1 + if task.duration then + duration=task.duration + if task.timestamp then + -- Time the task is running. + duration=task.duration-(timer.getAbsTime()-task.timestamp) + else + -- Time the task is supposed to run. + duration=task.duration + end + end + -- Output text for element. + if task.type==OPSGROUP.TaskType.SCHEDULED then + text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration) + elseif task.type==OPSGROUP.TaskType.WAYPOINT then + text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get()) + end + end + self:I(self.lid..text) + end + + --- + -- Missions + --- + + -- Current mission name. + if self.verbose>0 then + local Mission=self:GetMissionByID(self.currentmission) + + -- Current status. + local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none") + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + local Cstart= UTILS.SecondsToClock(mission.Tstart, true) + local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF" + text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", + i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets()) + end + self:I(self.lid..text) + end + + self:__Status(-10) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "ElementSpawned" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #ARMYGROUP.Element Element The group element. +function ARMYGROUP:onafterElementSpawned(From, Event, To, Element) + self:I(self.lid..string.format("Element spawned %s", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) + +end + +--- On after "ElementDead" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #ARMYGROUP.Element Element The group element. +function ARMYGROUP:onafterElementDead(From, Event, To, Element) + self:T(self.lid..string.format("Element dead %s.", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) +end + +--- On after "Spawned" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterSpawned(From, Event, To) + self:I(self.lid..string.format("Group spawned!")) + + if self.ai then + + -- Set default ROE and ROT options. + self:SetOptionROE(self.roe) + + end + + -- Get orientation. + self.Corientlast=self.group:GetUnit(1):GetOrientationX() + + self.depth=self.group:GetHeight() + + -- Update route. + self:Cruise() + +end + +--- On after "UpdateRoute" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number n Waypoint number. Default is next waypoint. +-- @param #number Speed Speed in knots. Default cruise speed. +-- @param #number Depth Depth in meters. Default 0 meters. +function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) + + -- Update route from this waypoint number onwards. + n=n or self:GetWaypointIndexNext(self.adinfinitum) + + -- Debug info. + self:T(self.lid..string.format("FF Update route n=%d", n)) + + -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. + self:_UpdateWaypointTasks(n) + + -- Waypoints. + local waypoints={} + + -- Depth for submarines. + local depth=Depth or 0 + + -- Get current speed in km/h. + local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() + + -- Current waypoint. + local current=self:GetCoordinate():WaypointNaval(speed, depth) + table.insert(waypoints, current) + + -- Add remaining waypoints to route. + for i=n, #self.waypoints do + local wp=self.waypoints[i] + + -- Set speed. + if i==n then + + if Speed then + wp.speed=UTILS.KnotsToMps(Speed) + elseif self.speedCruise then + wp.speed=UTILS.KmphToMps(self.speedCruise) + else + -- Take default waypoint speed. + end + + else + + if self.speedCruise then + wp.speed=UTILS.KmphToMps(self.speedCruise) + else + -- Take default waypoint speed. + end + + end + + -- Set depth. + wp.alt=-depth --Depth and -Depth or wp.alt + + -- Add waypoint. + table.insert(waypoints, wp) + end + + + if #waypoints>1 then + + self:I(self.lid..string.format("Updateing route: WP=%d, Speed=%.1f knots, depth=%d meters", #self.waypoints-n+1, UTILS.KmphToKnots(speed), depth)) + + -- Route group to all defined waypoints remaining. + self:Route(waypoints) + + else + + --- + -- No waypoints left + --- + + self:I(self.lid..string.format("No waypoints left")) + + if #self.waypoints>1 then + self:I(self.lid..string.format("Resuming route at first waypoint")) + self:__UpdateRoute(-1, 1, nil, self.depth) + end + + end + +end + +--- On after "Detour" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Coordinate Coordinate where to go. +-- @param #number Speed Speed in knots. Default cruise speed. +-- @param #number Depth Depth in meters. Default 0 meters. +-- @param #number ResumeRoute If true, resume route after detour point was reached. +function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute) + + -- Waypoints. + local waypoints={} + + -- Depth for submarines. + local depth=Depth or 0 + + -- Get current speed in km/h. + local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() + + -- Current waypoint. + local current=self:GetCoordinate():WaypointGround(Speed,Formation,DCSTasks) + table.insert(waypoints, current) + + -- At each waypoint report passing. + local Task=self.group:TaskFunction("ARMYGROUP._DetourReached", self, ResumeRoute) + + local detour=Coordinate:WaypointNaval(speed, depth, {Task}) + table.insert(waypoints, detour) + + self:Route(waypoints) + +end + +--- On after "DetourReached" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterDetourReached(From, Event, To) + self:I(self.lid.."Group reached detour coordinate.") +end + +--- Function called when a group is passing a waypoint. +--@param Wrapper.Group#GROUP group Group that passed the waypoint +--@param #ARMYGROUP navygroup Navy group object. +--@param #boolean resume Resume route. +function ARMYGROUP._DetourReached(group, navygroup, resume) + + -- Debug message. + local text=string.format("Group reached detour coordinate") + navygroup:I(navygroup.lid..text) + + if resume then + local indx=navygroup:GetWaypointIndexNext(true) + local speed=navygroup:GetSpeedToWaypoint(indx) + navygroup:UpdateRoute(indx, speed, navygroup.depth) + end + + navygroup:DetourReached() + +end + +--- On after "FullStop" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterFullStop(From, Event, To) + + -- Get current position. + local pos=self:GetCoordinate() + + -- Create a new waypoint. + local wp=pos:WaypointNaval(0) + + -- Create new route consisting of only this position ==> Stop! + self:Route({wp}) + +end + +--- On after "Cruise" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #number Speed Speed in knots. +function ARMYGROUP:onafterCruise(From, Event, To, Speed) + + self:UpdateRoute(nil, Speed, self.depth) + +end + + + +--- On after "Dead" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterDead(From, Event, To) + self:I(self.lid..string.format("Group dead!")) + + -- Delete waypoints so they are re-initialized at the next spawn. + self.waypoints=nil + self.groupinitialized=false + + -- Cancel all mission. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + self:MissionCancel(mission) + mission:GroupDead(self) + + end + + -- Stop + self:Stop() +end + +--- On after Start event. Starts the ARMYGROUP FSM and event handlers. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterStop(From, Event, To) + + -- Check if group is still alive. + if self:IsAlive() then + -- Destroy group. No event is generated. + self.group:Destroy(false) + end + + -- Handle events: + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.RemoveUnit) + + self.CallScheduler:Clear() + + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Events DCS +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function handling the birth of a unit. +-- @param #ARMYGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function ARMYGROUP:OnEventBirth(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + if self.respawning then + + local function reset() + self.respawning=nil + end + + -- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed. + -- TODO: Can I do this more rigorously? + self:ScheduleOnce(1, reset) + + else + + -- Get element. + local element=self:GetElementByName(unitname) + + -- Set element to spawned state. + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) + self:ElementSpawned(element) + + end + + end + +end + +--- Flightgroup event function handling the crash of a unit. +-- @param #ARMYGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function ARMYGROUP:OnEventDead(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + self:T(self.lid..string.format("EVENT: Unit %s dead!", EventData.IniUnitName)) + + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + self:I(self.lid..string.format("EVENT: Element %s dead ==> dead", element.name)) + self:ElementDead(element) + end + + end + +end + +--- Flightgroup event function handling the crash of a unit. +-- @param #ARMYGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function ARMYGROUP:OnEventRemoveUnit(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + if element then + self:I(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) + self:ElementDead(element) + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Routing +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add an a waypoint to the route. +-- @param #ARMYGROUP self +-- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. +-- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed. +-- @param #number wpnumber Waypoint number. Default at the end. +-- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. +-- @return #number Waypoint index. +function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) + + -- Waypoint number. Default is at the end. + wpnumber=wpnumber or #self.waypoints+1 + + if wpnumber>self.currentwp then + self.passedfinalwp=false + end + + -- Speed in knots. + speed=speed or self:GetSpeedCruise() + + -- Speed at waypoint. + local speedkmh=UTILS.KnotsToKmph(speed) + + -- Create a Naval waypoint. + local wp=coordinate:WaypointNaval(speedkmh) + + -- Add to table. + table.insert(self.waypoints, wpnumber, wp) + + -- Debug info. + self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) + + + -- Update route. + if updateroute==nil or updateroute==true then + self:_CheckGroupDone(1) + end + + return wpnumber +end + +--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. +-- @param #ARMYGROUP self +-- @return #ARMYGROUP self +function ARMYGROUP:_InitGroup() + + -- First check if group was already initialized. + if self.groupinitialized then + self:E(self.lid.."WARNING: Group was already initialized!") + return + end + + -- Get template of group. + self.template=self.group:GetTemplate() + + -- Define category. + self.isAircraft=false + self.isNaval=false + self.isGround=true + + -- Ships are always AI. + self.ai=true + + -- Is (template) group late activated. + self.isLateActivated=self.template.lateActivation + + -- Naval groups cannot be uncontrolled. + self.isUncontrolled=false + + -- Max speed in km/h. + self.speedmax=self.group:GetSpeedMax() + + -- Group ammo. + --self.ammo=self:GetAmmoTot() + + self.traveldist=0 + self.traveltime=timer.getAbsTime() + self.position=self:GetCoordinate() + + -- Radio parameters from template. + self.radioOn=true -- Radio is always on for ships. + self.radioFreq=tonumber(self.template.units[1].frequency)/1000000 + self.radioModu=tonumber(self.template.units[1].modulation)/1000000 + + -- If not set by the use explicitly yet, we take the template values as defaults. + if not self.radioFreqDefault then + self.radioFreqDefault=self.radioFreq + self.radioModuDefault=self.radioModu + end + + -- Set default formation. + if not self.formationDefault then + if self.ishelo then + self.formationDefault=ENUMS.Formation.RotaryWing.EchelonLeft.D300 + else + self.formationDefault=ENUMS.Formation.FixedWing.EchelonLeft.Group + end + end + + local units=self.group:GetUnits() + + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + local element={} --#ARMYGROUP.Element + element.name=unit:GetName() + element.typename=unit:GetTypeName() + element.status=OPSGROUP.ElementStatus.INUTERO + element.unit=unit + table.insert(self.elements, element) + + self:GetAmmoUnit(unit, false) + + if unit:IsAlive() then + self:ElementSpawned(element) + end + + end + + -- Get first unit. This is used to extract other parameters. + local unit=self.group:GetUnit(1) + + if unit then + + self.descriptors=unit:GetDesc() + + self.actype=unit:GetTypeName() + + -- Debug info. + local text=string.format("Initialized Navy Group %s:\n", self.groupname) + text=text..string.format("AC type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) + --text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) + --text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + + -- Init done. + self.groupinitialized=true + + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Check if group is done, i.e. +-- +-- * passed the final waypoint, +-- * no current task +-- * no current mission +-- * number of remaining tasks is zero +-- * number of remaining missions is zero +-- +-- @param #ARMYGROUP self +-- @param #number delay Delay in seconds. +function ARMYGROUP:_CheckGroupDone(delay) + + if self:IsAlive() and self.ai then + + if delay and delay>0 then + -- Delayed call. + self:ScheduleOnce(delay, ARMYGROUP._CheckGroupDone, self) + else + + if self.passedfinalwp then + + if #self.waypoints>1 and self.adinfinitum then + + local speed=self:GetSpeedToWaypoint(1) + + -- Start route at first waypoint. + self:__UpdateRoute(-1, 1, speed, self.depth) + + end + + else + + self:UpdateRoute(nil, nil, self.depth) + + end + + end + + end + +end + + +--- Get default cruise speed. +-- @param #ARMYGROUP self +-- @return #number Cruise speed (>0) in knots. +function ARMYGROUP:GetSpeedCruise() + return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) +end + +--- Returns a non-zero speed to the next waypoint (even if the waypoint speed is zero). +-- @param #ARMYGROUP self +-- @param #number indx Waypoint index. +-- @return #number Speed to next waypoint (>0) in knots. +function ARMYGROUP:GetSpeedToWaypoint(indx) + + local speed=self:GetWaypointSpeed(indx) + + if speed<=0.1 then + speed=self:GetSpeedCruise() + end + + return speed +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c4f53b443..1d0f2c05c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2956,7 +2956,7 @@ end -- @param #number speed Speed in knots. Default 350 kts. -- @param #number wpnumber Waypoint number. Default at the end. -- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. --- @return #number Waypoint index. +-- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function FLIGHTGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) -- Waypoint number. Default is at the end. @@ -2973,43 +2973,24 @@ function FLIGHTGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) local speedkmh=UTILS.KnotsToKmph(speed) -- Create air waypoint. - local wp=coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, string.format("Added Waypoint #%d", wpnumber)) + local name=string.format("Added Waypoint #%d", wpnumber) + local wp=coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, name) - -- Add to table. - table.insert(self.waypoints, wpnumber, wp) + -- Create waypoint data table. + local waypoint=self:_CreateWaypoint(wp) + + -- Add waypoint to table. + self:_AddWaypoint(waypoint, wpnumber) -- Debug info. self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) - -- Shift all waypoint tasks after the inserted waypoint. - for _,_task in pairs(self.taskqueue) do - local task=_task --Ops.OpsGroup#OPSGROUP.Task - if task.type==OPSGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then - task.waypoint=task.waypoint+1 - end - end - - -- Shift all mission waypoints after the inserted waypoint. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Get mission waypoint index. - local wpidx=mission:GetGroupWaypointIndex(self) - - -- Increase number if this waypoint lies in the future. - if wpidx and wpidx>=wpnumber then - mission:SetGroupWaypointIndex(self, wpidx+1) - end - - end - -- Update route. if updateroute==nil or updateroute==true then - --self:_CheckGroupDone(1) self:__UpdateRoute(-1) end - return wpnumber + return waypoint end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index d6fe5816f..046cf4f8b 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -934,7 +934,7 @@ end -- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number wpnumber Waypoint number. Default at the end. -- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. --- @return #number Waypoint index. +-- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) -- Waypoint number. Default is at the end. @@ -952,41 +952,22 @@ function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) -- Create a Naval waypoint. local wp=coordinate:WaypointNaval(speedkmh) - - -- Add to table. - table.insert(self.waypoints, wpnumber, wp) + + -- Create waypoint data table. + local waypoint=self:_CreateWaypoint(wp) + + -- Add waypoint to table. + self:_AddWaypoint(waypoint, wpnumber) -- Debug info. self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) - - -- Shift all waypoint tasks after the inserted waypoint. - for _,_task in pairs(self.taskqueue) do - local task=_task --Ops.OpsGroup#OPSGROUP.Task - if task.type==OPSGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then - task.waypoint=task.waypoint+1 - end - end - -- Shift all mission waypoints after the inserted waypoint. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Get mission waypoint index. - local wpidx=mission:GetGroupWaypointIndex(self) - - -- Increase number if this waypoint lies in the future. - if wpidx and wpidx>=wpnumber then - mission:SetGroupWaypointIndex(self, wpidx+1) - end - - end - -- Update route. if updateroute==nil or updateroute==true then self:_CheckGroupDone(1) end - return wpnumber + return waypoint end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 65422467a..c625458a6 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -39,6 +39,7 @@ -- @field #number speedmax Max speed in km/h. -- @field #number speedCruise Cruising speed in km/h. -- @field #boolean passedfinalwp Group has passed the final waypoint. +-- @field #number wpcounter Running number counting waypoints. -- @field #boolean respawning Group is being respawned. -- @field Core.Set#SET_ZONE checkzones Set of zones. -- @field Core.Set#SET_ZONE inzones Set of zones in which the group is currently in. @@ -117,6 +118,7 @@ OPSGROUP = { inzones = nil, groupinitialized = nil, respawning = nil, + wpcounter = 1, } --- Status of group element. @@ -201,6 +203,19 @@ OPSGROUP.TaskType={ -- @field #number MissilesCR Amount of cruise missiles. -- @field #number MissilesBM Amount of ballistic missiles. +--- Waypoint data. +-- @type OPSGROUP.Waypoint +-- @field #table wp DCS waypoint table. +-- @field Core.Point#COORDINATE coordinate Waypoint coordinate. +-- @field #number speed Speed in m/s. +-- @field #number altitude Altitude in meters. For submaries use negative sign for depth. +-- @field #number index Waypoint index. This might change as waypoints are added and removed. +-- @field #number uid Waypoint's unit id, which is a running number. +-- @field #boolean onroad If true, ground group takes a road. +-- @field #number formation The formation for this waypoint. +-- @field #boolean detour If true, this waypoint is not part of the normal route. +-- @field #string action Waypoint action (turning point, etc.). Ground groups have the formation here. + --- NavyGroup version. -- @field #string version OPSGROUP.version="0.1.0" @@ -632,6 +647,70 @@ end -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get the waypoint from its unique ID. +-- @param #OPSGROUP self +-- @param #number uid Waypoint unique ID. +-- @return #OPSGROUP.Waypoint Waypoint data. +function OPSGROUP:GetWaypointByID(uid) + + for _,_waypoint in pairs(self.waypoints) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + if waypoint.uid==uid then + return waypoint + end + end + + return nil +end + +--- Get the waypoint from its index. +-- @param #OPSGROUP self +-- @param #number index Waypoint index. +-- @return #OPSGROUP.Waypoint Waypoint data. +function OPSGROUP:GetWaypointByIndex(index) + + for i,_waypoint in pairs(self.waypoints) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + if i==index then + return waypoint + end + end + + return nil +end + +--- Get the waypoint index (its position in the current waypoints table). +-- @param #OPSGROUP self +-- @param #number uid Waypoint unique ID. +-- @return #OPSGROUP.Waypoint Waypoint data. +function OPSGROUP:GetWaypointIndex(uid) + + for i,_waypoint in pairs(self.waypoints) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + if waypoint.uid==uid then + return i + end + end + + return nil +end + +--- Remove a waypoint with a ceratin UID. +-- @param #OPSGROUP self +-- @param #number uid Waypoint UID. +-- @return #OPSGROUP self +function OPSGROUP:RemoveWaypointByID(uid) + + local index=self:GetWaypointIndex(uid) + + if index then + self:RemoveWaypoint(index) + + end + + return self +end + --- Remove a waypoint. -- @param #OPSGROUP self -- @param #number wpindex Waypoint number. @@ -652,28 +731,6 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- Debug info. self:I(self.lid..string.format("Removing waypoint %d. N %d-->%d", wpindex, N, n)) - -- Shift all waypoint tasks after the removed waypoint. - for _,_task in pairs(self.taskqueue) do - local task=_task --#OPSGROUP.Task - if task.type==OPSGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>wpindex then - task.waypoint=task.waypoint-1 - end - end - - -- Shift all mission waypoints after the removerd waypoint. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- Get mission waypoint index. - local wpidx=mission:GetGroupWaypointIndex(self) - - -- Reduce number if this waypoint lies in the future. - if wpidx and wpidx>wpindex then - mission:SetGroupWaypointIndex(self, wpidx-1) - end - end - - -- Waypoint was not reached yet. if wpindex > self.currentwp then @@ -858,35 +915,46 @@ end -- @return #OPSGROUP.Task The task structure. function OPSGROUP:AddTaskWaypoint(task, waypointindex, description, prio, duration) - -- Increase counter. - self.taskcounter=self.taskcounter+1 - - -- Task data structure. - local newtask={} --#OPSGROUP.Task - newtask.description=description - newtask.status=OPSGROUP.TaskStatus.SCHEDULED - newtask.dcstask=task - newtask.prio=prio or 50 - newtask.id=self.taskcounter - newtask.duration=duration - newtask.time=0 - newtask.waypoint=waypointindex or (self.currentwp and self.currentwp+1 or 2) - newtask.type=OPSGROUP.TaskType.WAYPOINT - newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id)) - newtask.stopflag:Set(0) - - -- Add to table. - table.insert(self.taskqueue, newtask) + -- Index. + waypointindex=waypointindex or (self.currentwp and self.currentwp+1 or 2) - -- Info. - self:I(self.lid..string.format("Adding WAYPOINT task %s at WP %d", newtask.description, newtask.waypoint)) - self:T3({newtask=newtask}) + -- Get waypoint + local waypoint=self:GetWaypointByIndex(waypointindex) - -- Update route. - --self:_CheckGroupDone(1) - self:__UpdateRoute(-1) + if waypoint then - return newtask + -- Increase counter. + self.taskcounter=self.taskcounter+1 + + -- Task data structure. + local newtask={} --#OPSGROUP.Task + newtask.description=description + newtask.status=OPSGROUP.TaskStatus.SCHEDULED + newtask.dcstask=task + newtask.prio=prio or 50 + newtask.id=self.taskcounter + newtask.duration=duration + newtask.time=0 + newtask.waypoint=waypoint.uid + newtask.type=OPSGROUP.TaskType.WAYPOINT + newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id)) + newtask.stopflag:Set(0) + + -- Add to table. + table.insert(self.taskqueue, newtask) + + -- Info. + self:I(self.lid..string.format("Adding WAYPOINT task %s at WP %d", newtask.description, newtask.waypoint)) + self:T3({newtask=newtask}) + + -- Update route. + --self:_CheckGroupDone(1) + self:__UpdateRoute(-1) + + return newtask + end + + return nil end --- Add an *enroute* task. @@ -1215,20 +1283,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) -- Call task done function. self:TaskDone(Task) - - - --[[ - local mission=self:GetMissionByTaskID(Task.id) - - -- Is this a waypoint task? - if Task.type==OPSGROUP.TaskType.WAYPOINT and Task.waypoint then - -- Check that this is a mission waypoint and no other tasks are defined here. - if mission and #self:GetTasksWaypoint(Task.waypoint)==0 then - self:RemoveWaypoint(Task.waypoint) - end - end - ]] end else @@ -1710,7 +1765,7 @@ function OPSGROUP:RouteToMission(mission, delay) end -- Add waypoint. - self:AddWaypoint(waypointcoord, UTILS.KmphToKnots(self.speedCruise), nextwaypoint, false) + local waypoint=self:AddWaypoint(waypointcoord, UTILS.KmphToKnots(self.speedCruise), nextwaypoint, false) -- Special for Troop transport. if mission.type==AUFTRAG.Type.TROOPTRANSPORT then @@ -1738,7 +1793,7 @@ function OPSGROUP:RouteToMission(mission, delay) mission:SetGroupWaypointTask(self, waypointtask) -- Set waypoint index. - mission:SetGroupWaypointIndex(self, nextwaypoint) + mission:SetGroupWaypointIndex(self, waypoint.uid) --- -- Mission Specific Settings @@ -1848,13 +1903,14 @@ end -- @param #string To To state. -- @param #number n Waypoint passed. -- @param #number N Total number of waypoints. -function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N) +-- @param #OPSGROUP.Waypoint Waypoint Waypoint data passed. +function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, Waypoint) local text=string.format("Group passed waypoint %d/%d", n, N) self:T(self.lid..text) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) -- Get all waypoint tasks. - local tasks=self:GetTasksWaypoint(n) + local tasks=self:GetTasksWaypoint(Waypoint.uid) -- Debug info. local text=string.format("WP %d/%d tasks:", n, N) @@ -2125,6 +2181,43 @@ end -- Waypoints & Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Initialize Mission Editor waypoints. +-- @param #OPSGROUP self +-- @param #table waypoint DCS waypoint data table. +-- @return #OPSGROUP.Waypoint Waypoint data. +function OPSGROUP:_CreateWaypoint(waypoint) + + local wp={} --#OPSGROUP.Waypoint + + wp.wp=waypoint + wp.uid=self.wpcounter + + wp.altitude=altitude + wp.speed=speed + wp.detour=detour + wp.formation=formation + wp.onroad=onroad + + wp.coordinate=coordinate + + self.wpcounter=self.wpcounter+1 + + return wp +end + +--- Initialize Mission Editor waypoints. +-- @param #OPSGROUP self +-- @param #OPSGROUP.Waypoint waypoint Waypoint data. +-- @param #number wpnumber Waypoint index/number. Default is as last waypoint. +function OPSGROUP:_AddWaypoint(waypoint, wpnumber) + + wpnumber=wpnumber or #self.waypoints+1 + + -- Add waypoint to table. + tabel.insert(self.waypoints, wpnumber, waypoint) + +end + --- Initialize Mission Editor waypoints. -- @param #OPSGROUP self -- @param #table waypoints Table of waypoints. Default is from group template. @@ -2134,8 +2227,17 @@ function OPSGROUP:InitWaypoints(waypoints) -- Template waypoints. self.waypoints0=self.group:GetTemplateRoutePoints() - -- Waypoints of group as defined in the ME. - self.waypoints=waypoints or UTILS.DeepCopy(self.waypoints0) + -- Waypoints + self.waypoints={} + + for index,wp in pairs(self.waypoints0) do + + local waypoint=self:_CreateWaypoint() + + self:_AddWaypoint(waypoint) + + + end -- Debug info. self:I(self.lid..string.format("Initializing %d waypoints", #self.waypoints)) @@ -2201,7 +2303,8 @@ function OPSGROUP:_UpdateWaypointTasks(n) local waypoints=self.waypoints local nwaypoints=#waypoints - for i,wp in pairs(waypoints) do + for i,_wp in pairs(waypoints) do + local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint if i>=n or nwaypoints==1 then @@ -2212,11 +2315,11 @@ function OPSGROUP:_UpdateWaypointTasks(n) local taskswp={} -- At each waypoint report passing. - local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, i) + local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, i, wp.uid) table.insert(taskswp, TaskPassingWaypoint) -- Waypoint task combo. - wp.task=self.group:TaskCombo(taskswp) + wp.wp.task=self.group:TaskCombo(taskswp) end @@ -2232,19 +2335,23 @@ end --@param Wrapper.Group#GROUP group Group that passed the waypoint --@param #OPSGROUP opsgroup Ops group object. --@param #number i Waypoint number that has been reached. -function OPSGROUP._PassingWaypoint(group, opsgroup, i) +--@param #number uid Waypoint UID. +function OPSGROUP._PassingWaypoint(group, opsgroup, i, uid) local final=#opsgroup.waypoints or 1 -- Debug message. - local text=string.format("Group passing waypoint %d of %d", i, final) + local text=string.format("Group passing waypoint %d of %d, uid=%d", i, final, uid) opsgroup:I(opsgroup.lid..text) -- Set current waypoint. opsgroup.currentwp=i + + -- Get waypoint data. + local waypoint=opsgroup:GetWaypointByID(uid) -- Trigger PassingWaypoint event. - opsgroup:PassingWaypoint(i, final) + opsgroup:PassingWaypoint(i, final, waypoint) end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 99ac9c9b1..dd1da2d86 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -133,7 +133,7 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) self:SetEngagementRange() -- Everyone can ORBIT. - self:AddMissonCapability(AUFTRAG.Type.ORBIT) + self:AddMissionCapability(AUFTRAG.Type.ORBIT) self.attribute=self.templategroup:GetAttribute() @@ -261,7 +261,7 @@ end -- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type. -- @param #number Performance Performance describing how good this mission can be performed. Higher is better. Default 50. Max 100. -- @return #SQUADRON self -function SQUADRON:AddMissonCapability(MissionTypes, Performance) +function SQUADRON:AddMissionCapability(MissionTypes, Performance) -- Ensure Missiontypes is a table. if MissionTypes and type(MissionTypes)~="table" then diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index ece8a111b..74a7b4c5b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -353,7 +353,7 @@ UTILS.MpsToMiph = function( mps ) end --- Convert meters per second to knots. --- @param #number knots Speed in m/s. +-- @param #number mps Speed in m/s. -- @return #number Speed in knots. UTILS.MpsToKnots = function( mps ) return mps * 1.94384 --3600 / 1852 From ec809085b42fa9413af6c32e954ef7d2c6485ecf Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 22 Jul 2020 01:05:24 +0200 Subject: [PATCH 13/79] Waypoints --- Moose Development/Moose/Core/Astar.lua | 30 ++++++++++++------ Moose Development/Moose/Ops/FlightGroup.lua | 23 ++++++++++---- Moose Development/Moose/Ops/NavyGroup.lua | 4 +-- Moose Development/Moose/Ops/OpsGroup.lua | 35 +++++++++------------ 4 files changed, 53 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 3ee069619..409bed41b 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -141,22 +141,30 @@ end -- @param #ASTAR.Node nodeB function ASTAR.LoS(nodeA, nodeB) - local los=nodeA.coordinate:IsLOS(nodeB.coordinate, 0.5) + local offset=0.1 + + local dx=200 + local dy=dx + + local cA=nodeA.coordinate:SetAltitude(0, true) + local cB=nodeB.coordinate:SetAltitude(0, true) + + local los=cA:IsLOS(cB, offset) if los then - local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) + local heading=cA:HeadingTo(cB) - local Ap=nodeA.coordinate:Translate(100, heading+90) - local Bp=nodeA.coordinate:Translate(100, heading+90) + local Ap=cA:Translate(dx, heading+90) + local Bp=cB:Translate(dx, heading+90) - los=Ap:IsLOS(Bp, 0.5) + los=Ap:IsLOS(Bp, offset) if los then - local Am=nodeA.coordinate:Translate(100, heading-90) - local Bm=nodeA.coordinate:Translate(100, heading-90) + local Am=cA:Translate(dy, heading-90) + local Bm=cB:Translate(dy, heading-90) - los=Am:IsLOS(Bm, 0.5) + los=Am:IsLOS(Bm, offset) end end @@ -170,8 +178,9 @@ end --- Find the closest node from a given coordinate. -- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate. --- @return #ASTAR.Node Cloest node to the coordinate. +-- @param #number DeltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters. +-- @param #number DeltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as DeltaX. +-- @return #ASTAR self function ASTAR:CreateGrid() local Dx=20000 @@ -218,6 +227,7 @@ function ASTAR:CreateGrid() end env.info("FF Done building grid!") + return self end --- Find the closest node from a given coordinate. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1d0f2c05c..c08bf7a22 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1699,7 +1699,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) local speed=self.group and self.group:GetVelocityKMH() or 100 -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. - local current=self.group:GetCoordinate():WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speed, true, nil, {}, "Current") + local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speed, true, nil, {}, "Current") table.insert(wp, current) -- Add remaining waypoints to route. @@ -1710,7 +1710,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) -- Debug info. local hb=self.homebase and self.homebase:GetName() or "unknown" local db=self.destbase and self.destbase:GetName() or "unknown" - self:T(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db)) + self:I(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db)) if #wp>1 then @@ -2910,15 +2910,26 @@ end --- Initialize Mission Editor waypoints. -- @param #FLIGHTGROUP self --- @param #table waypoints Table of waypoints. Default is from group template. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:InitWaypoints(waypoints) +function FLIGHTGROUP:InitWaypoints() -- Template waypoints. self.waypoints0=self.group:GetTemplateRoutePoints() + + self:I(self.waypoints0) - -- Waypoints of group as defined in the ME. - self.waypoints=waypoints or UTILS.DeepCopy(self.waypoints0) + -- Waypoints + self.waypoints={} + + for index,wp in pairs(self.waypoints0) do + + env.info("FF index "..index) + + local waypoint=self:_CreateWaypoint(wp) + + self:_AddWaypoint(waypoint) + + end -- Get home and destination airbases from waypoints. self.homebase=self.homebase or self:GetHomebaseFromWaypoints() diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 046cf4f8b..bab3034c9 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -539,8 +539,8 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Add remaining waypoints to route. for i=n, #self.waypoints do - local wp=self.waypoints[i] - + local wp=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint + -- Set speed. if i==n then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c625458a6..29845a1f5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2185,24 +2185,17 @@ end -- @param #OPSGROUP self -- @param #table waypoint DCS waypoint data table. -- @return #OPSGROUP.Waypoint Waypoint data. -function OPSGROUP:_CreateWaypoint(waypoint) - - local wp={} --#OPSGROUP.Waypoint +function OPSGROUP:_CreateWaypoint(waypoint, detour, onroad, formation) - wp.wp=waypoint - wp.uid=self.wpcounter - - wp.altitude=altitude - wp.speed=speed - wp.detour=detour - wp.formation=formation - wp.onroad=onroad - - wp.coordinate=coordinate + waypoint.uid=self.wpcounter + waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) + waypoint.detour=detour and detour or false + waypoint.formation=formation + waypoint.onroad=onroad and onroad or false self.wpcounter=self.wpcounter+1 - return wp + return waypoint end --- Initialize Mission Editor waypoints. @@ -2212,17 +2205,18 @@ end function OPSGROUP:_AddWaypoint(waypoint, wpnumber) wpnumber=wpnumber or #self.waypoints+1 + + env.info(string.format("adding waypoint at index=%d", wpnumber)) -- Add waypoint to table. - tabel.insert(self.waypoints, wpnumber, waypoint) + table.insert(self.waypoints, wpnumber, waypoint) end --- Initialize Mission Editor waypoints. -- @param #OPSGROUP self --- @param #table waypoints Table of waypoints. Default is from group template. -- @return #OPSGROUP self -function OPSGROUP:InitWaypoints(waypoints) +function OPSGROUP:InitWaypoints() -- Template waypoints. self.waypoints0=self.group:GetTemplateRoutePoints() @@ -2232,11 +2226,10 @@ function OPSGROUP:InitWaypoints(waypoints) for index,wp in pairs(self.waypoints0) do - local waypoint=self:_CreateWaypoint() + local waypoint=self:_CreateWaypoint(wp) self:_AddWaypoint(waypoint) - - + end -- Debug info. @@ -2319,7 +2312,7 @@ function OPSGROUP:_UpdateWaypointTasks(n) table.insert(taskswp, TaskPassingWaypoint) -- Waypoint task combo. - wp.wp.task=self.group:TaskCombo(taskswp) + wp.task=self.group:TaskCombo(taskswp) end From 98971736f842608374478c7049eb332929c70887 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 23 Jul 2020 00:24:59 +0200 Subject: [PATCH 14/79] Astar --- Moose Development/Moose/Core/Astar.lua | 481 +++++++++++++------ Moose Development/Moose/Ops/AirWing.lua | 147 ------ Moose Development/Moose/Ops/ChiefOfStaff.lua | 9 +- Moose Development/Moose/Ops/Intelligence.lua | 12 +- Moose Development/Moose/Ops/NavyGroup.lua | 3 +- Moose Development/Moose/Ops/OpsGroup.lua | 28 +- Moose Development/Moose/Ops/Squadron.lua | 2 +- Moose Development/Moose/Wrapper/Group.lua | 20 + 8 files changed, 375 insertions(+), 327 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 409bed41b..6650e15dc 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -21,10 +21,11 @@ -- @field #ASTAR.Node endNode End node. -- @field Core.Point#COORDINATE startCoord Start coordinate. -- @field Core.Point#COORDINATE endCoord End coordinate. --- @field #func CheckNodeValid Function to check if a node is valid. +-- @field #func ValidNeighbourFunc Function to check if a node is valid. +-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function. -- @extends Core.Base#BASE ---- Be surprised! +--- When nothing goes right... Go left! -- -- === -- @@ -42,7 +43,6 @@ ASTAR = { Debug = nil, lid = nil, nodes = {}, - CheckNodeValid = nil, } --- Defence condition. @@ -56,19 +56,20 @@ ASTAR.INF=1/0 --- ASTAR class version. -- @field #string version -ASTAR.version="0.0.1" +ASTAR.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: A lot. +-- TODO: Add more valid neighbour functions. +-- TODO: Write docs. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new ASTAR object and start the FSM. +--- Create a new ASTAR object. -- @param #ASTAR self -- @return #ASTAR self function ASTAR:New() @@ -76,6 +77,9 @@ function ASTAR:New() -- Inherit everything from INTEL class. local self=BASE:Inherit(self, BASE:New()) --#ASTAR + self.lid="ASTAR | " + + self.Debug=true return self end @@ -95,7 +99,7 @@ function ASTAR:SetStartCoordinate(Coordinate) return self end ---- Set coordinate from where to go. +--- Set coordinate where you want to go. -- @param #ASTAR self -- @param Core.Point#COORDINATE Coordinate end coordinate. -- @return #ASTAR self @@ -106,9 +110,9 @@ function ASTAR:SetEndCoordinate(Coordinate) return self end ---- Add a node. +--- Create a node from a given coordinate. -- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate The coordinate. +-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node. -- @return #ASTAR.Node The node. function ASTAR:GetNodeFromCoordinate(Coordinate) @@ -121,9 +125,9 @@ function ASTAR:GetNodeFromCoordinate(Coordinate) end ---- Add a node. +--- Add a node to the table of grid nodes. -- @param #ASTAR self --- @param #ASTAR.Node Node The node to be added to the nodes table. +-- @param #ASTAR.Node Node The node to be added. -- @return #ASTAR self function ASTAR:AddNode(Node) @@ -132,18 +136,171 @@ function ASTAR:AddNode(Node) return self end +--- Check if the coordinate of a node has is at a valid surface type. +-- @param #ASTAR self +-- @param #ASTAR.Node Node The node to be added. +-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid. +-- @return #boolean If true, surface type of node is valid. +function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes) + + if SurfaceTypes then + + if type(SurfaceTypes)~="table" then + SurfaceTypes={SurfaceTypes} + end + + for _,surface in pairs(SurfaceTypes) do + if surface==Node.surfacetype then + return true + end + end + + return false + + else + return true + end + +end + +--- Set valid neighbours to require line of sight between two nodes. +-- @param #ASTAR self +-- @param #number CorridorWidth Width of LoS corridor in meters. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourLoS(CorridorWidth) + + self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth) + + return self +end + +--- Add a function to determine if a neighbour of a node is valid. +-- @param #ASTAR self +-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid. +-- @param ... Condition function arguments if any. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...) + + self.ValidNeighbourFunc=NeighbourFunction + + self.ValidNeighbourArg={} + if arg then + self.ValidNeighbourArg=arg + end + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User functions +-- Grid functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Find the closest node from a given coordinate. --- @param #ASTAR.Node nodeA --- @param #ASTAR.Node nodeB -function ASTAR.LoS(nodeA, nodeB) +--- Create a rectangular grid of nodes between star and end coordinate. +-- The coordinate system is oriented along the line between start and end point. +-- @param #ASTAR self +-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed. +-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km). +-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km). +-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters. +-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX. +-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes. +-- @return #ASTAR self +function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid) + + -- Note that internally + -- x coordinate is z: x-->z Line from start to end + -- y coordinate is x: y-->x Perpendicular + + -- Grid length and width. + local Dz=SpaceX or 10000 + local Dx=BoxHY and BoxHY/2 or 20000 + + -- Increments. + local dz=deltaX or 2000 + local dx=deltaY or dz + + -- Heading from start to end coordinate. + local angle=self.startCoord:HeadingTo(self.endCoord) + + --Distance between start and end. + local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz + + -- Origin of map. Needed to translate back to wanted position. + local co=COORDINATE:New(0, 0, 0) + local do1=co:Get2DDistance(self.startCoord) + local ho1=co:HeadingTo(self.startCoord) + + -- Start of grid. + local xmin=-Dx + local zmin=-Dz + + -- Number of grid points. + local nz=dist/dz+1 + local nx=2*Dx/dx+1 + + -- Debug info. + local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz) + self:I(self.lid..text) + MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + + -- Loop over x and z coordinate to create a 2D grid. + for i=1,nx do + + -- x coordinate perpendicular to z. + local x=xmin+dx*(i-1) + + for j=1,nz do + + -- z coordinate connecting start and end. + local z=zmin+dz*(j-1) + + -- Rotate 2D. + local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle) + + -- Coordinate of the node. + local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true) + + -- Create a node at this coordinate. + local node=self:GetNodeFromCoordinate(c) + + -- Check if node has valid surface type. + if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then + + if MarkGrid then + c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype)) + end + + -- Add node to grid. + self:AddNode(node) + + end + + end + end + + -- Debug info. + local text=string.format("Done building grid!") + self:I(self.lid..text) + MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Valid neighbour functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function to check if two nodes have line of sight (LoS). +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @param #number corridor (Optional) Width of corridor in meters. +-- @return #boolean If true, two nodes have LoS. +function ASTAR.LoS(nodeA, nodeB, corridor) local offset=0.1 - local dx=200 + local dx=corridor and corridor/2 or nil local dy=dx local cA=nodeA.coordinate:SetAltitude(0, true) @@ -151,7 +308,7 @@ function ASTAR.LoS(nodeA, nodeB) local los=cA:IsLOS(cB, offset) - if los then + if los and corridor then local heading=cA:HeadingTo(cB) local Ap=cA:Translate(dx, heading+90) @@ -173,62 +330,9 @@ function ASTAR.LoS(nodeA, nodeB) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User functions +-- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Find the closest node from a given coordinate. --- @param #ASTAR self --- @param #number DeltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters. --- @param #number DeltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as DeltaX. --- @return #ASTAR self -function ASTAR:CreateGrid() - - local Dx=20000 - local Dz=10000 - local delta=2000 - - local angle=self.startCoord:HeadingTo(self.endCoord) - local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz - - local co=COORDINATE:New(0, 0, 0) - - local do1=co:Get2DDistance(self.startCoord) - local ho1=co:HeadingTo(self.startCoord) - - local xmin=-Dx - local zmin=-Dz - - local nz=dist/delta+1 - local nx=2*Dx/delta+1 - - env.info(string.format("FF building grid with nx=%d ny=%d total=%d nodes. Angle=%d, dist=%d meters", nx, nz, nx*nz, angle, dist)) - for i=1,nx do - - local x=xmin+delta*(i-1) - - for j=1,nz do - - local z=zmin+delta*(j-1) - - local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle) - - local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true) - - if c:IsSurfaceTypeWater() then - - --c:MarkToAll(string.format("i=%d, j=%d", i, j)) - - local node=self:GetNodeFromCoordinate(c) - self:AddNode(node) - - end - - end - end - env.info("FF Done building grid!") - - return self -end --- Find the closest node from a given coordinate. -- @param #ASTAR self @@ -276,41 +380,147 @@ function ASTAR:FindEndNode() return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Main A* pathfinding function +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Function +--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates. +-- @param #ASTAR self +-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it. +-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it. +-- @return #table Table of nodes from start to finish. +function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) + + self:FindStartNode() + self:FindEndNode() + + local nodes=self.nodes + local start=self.startNode + local goal=self.endNode + + local closedset = {} + local openset = { start } + local came_from = {} + + local g_score, f_score = {}, {} + + g_score[start]=0 + f_score[start]=g_score[start]+self:HeuristicCost(start, goal) + + -- Set start time. + local T0=timer.getAbsTime() + + -- Debug message. + local text=string.format("Starting A* pathfinding") + self:I(self.lid..text) + MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + while #openset > 0 do + + local current=self:LowestFscore(openset, f_score) + + -- Check if we are at the end node. + if current==goal then + + local path=self:UnwindPath({}, came_from, goal) + + if not ExcludeEndNode then + table.insert(path, goal) + end + + if ExcludeStartNode then + table.remove(path, 1) + end + + -- Set end time. + local T9=timer.getAbsTime() + + -- Debug message. + local text=string.format("Found path with %d nodes", #path) + self:I(self.lid..text) + MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) + + return path + end + + self:RemoveNode(openset, current) + table.insert(closedset, current) + + local neighbors=self:NeighbourNodes(current, nodes) + + -- Loop over neighbours. + for _,neighbor in ipairs(neighbors) do + + if self:NotIn(closedset, neighbor) then + + local tentative_g_score=g_score[current]+self:DistNodes(current, neighbor) + + if self:NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then + + came_from[neighbor]=current + + g_score[neighbor]=tentative_g_score + f_score[neighbor]=g_score[neighbor]+self:HeuristicCost(neighbor, goal) + + if self:NotIn(openset, neighbor) then + table.insert(openset, neighbor) + end + + end + end + end + end + + -- Debug message. + local text=string.format("WARNING: Could NOT find valid path!") + self:I(self.lid..text) + MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) + + return nil -- no valid path +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- A* pathfinding helper functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Calculate 2D distance between two nodes. -- @param #ASTAR self -- @param #ASTAR.Node nodeA Node A. -- @param #ASTAR.Node nodeB Node B. -- @return #number Distance between nodes in meters. -function ASTAR:DistNodes ( nodeA, nodeB ) +function ASTAR:DistNodes(nodeA, nodeB) return nodeA.coordinate:Get2DDistance(nodeB.coordinate) end ---- Function +--- Heuristic cost function to go from node A to node B. That is simply the distance here. -- @param #ASTAR self -- @param #ASTAR.Node nodeA Node A. -- @param #ASTAR.Node nodeB Node B. -- @return #number Distance between nodes in meters. -function ASTAR:HeuristicCost( nodeA, nodeB ) +function ASTAR:HeuristicCost(nodeA, nodeB) return self:DistNodes(nodeA, nodeB) end ---- Function +--- Check if going from a node to a neighbour is possible. -- @param #ASTAR self -function ASTAR:is_valid_node ( node, neighbor ) +-- @param #ASTAR.Node node A node. +-- @param #ASTAR.Node neighbor Neighbour node. +-- @return #boolean If true, transition between nodes is possible. +function ASTAR:IsValidNeighbour(node, neighbor) - self.CheckNodeValid=ASTAR.LoS - - if self.CheckNodeValid then - return self.CheckNodeValid(node, neighbor) + if self.ValidNeighbourFunc then + + return self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg)) + else return true end + end --- Function -- @param #ASTAR self -function ASTAR:lowest_f_score(set, f_score) +function ASTAR:LowestFscore(set, f_score) local lowest, bestNode = ASTAR.INF, nil @@ -326,25 +536,37 @@ function ASTAR:lowest_f_score(set, f_score) return bestNode end ---- Function +--- Function to get valid neighbours of a node. -- @param #ASTAR self -function ASTAR:neighbor_nodes(theNode, nodes) +-- @param #ASTAR.Node theNode The node. +-- @param #table nodes Possible neighbours. +-- @param #table Valid neighbour nodes. +function ASTAR:NeighbourNodes(theNode, nodes) local neighbors = {} for _, node in ipairs ( nodes ) do - - if theNode ~= node and self:is_valid_node ( theNode, node ) then - table.insert ( neighbors, node ) + if theNode~=node then + + local isvalid=self:IsValidNeighbour(theNode, node) + + if isvalid then + table.insert(neighbors, node) + end + end end + return neighbors end ---- Function +--- Function to check if a node is not in a set. -- @param #ASTAR self -function ASTAR:not_in ( set, theNode ) +-- @param #table set Set of nodes. +-- @param #ASTAR.Node theNode The node to check. +-- @return #boolean If true, the node is not in the set. +function ASTAR:NotIn(set, theNode) for _, node in ipairs ( set ) do if node == theNode then @@ -355,9 +577,11 @@ function ASTAR:not_in ( set, theNode ) return true end ---- Function +--- Function to remove a node from a set. -- @param #ASTAR self -function ASTAR:remove_node(set, theNode) +-- @param #table set Set of nodes. +-- @param #ASTAR.Node theNode The node to check. +function ASTAR:RemoveNode(set, theNode) for i, node in ipairs ( set ) do if node == theNode then @@ -365,11 +589,16 @@ function ASTAR:remove_node(set, theNode) set [ #set ] = nil break end - end + end + end ---- Function +--- Unwind path function. -- @param #ASTAR self +-- @param #table flat_path Flat path. +-- @param #table map Map. +-- @param #ASTAR.Node current_node The current node. +-- @return #table Unwinded path. function ASTAR:UnwindPath( flat_path, map, current_node ) if map [ current_node ] then @@ -380,70 +609,6 @@ function ASTAR:UnwindPath( flat_path, map, current_node ) end end ----------------------------------------------------------------- --- pathfinding functions ----------------------------------------------------------------- - ---- Function --- @param #ASTAR self -function ASTAR:GetPath() - - self:FindStartNode() - self:FindEndNode() - - local nodes=self.nodes - local start=self.startNode - local goal=self.endNode - - local closedset = {} - local openset = { start } - local came_from = {} - - local g_score, f_score = {}, {} - - g_score [ start ] = 0 - - f_score [ start ] = g_score [ start ] + self:HeuristicCost ( start, goal ) - - while #openset > 0 do - - local current = self:lowest_f_score ( openset, f_score ) - - if current == goal then - local path = self:UnwindPath ( {}, came_from, goal ) - table.insert(path, goal) - return path - end - - self:remove_node( openset, current ) - table.insert ( closedset, current ) - - local neighbors = self:neighbor_nodes( current, nodes ) - - for _, neighbor in ipairs ( neighbors ) do - - if self:not_in ( closedset, neighbor ) then - - local tentative_g_score = g_score [ current ] + self:DistNodes ( current, neighbor ) - - if self:not_in ( openset, neighbor ) or tentative_g_score < g_score [ neighbor ] then - - came_from [ neighbor ] = current - g_score [ neighbor ] = tentative_g_score - f_score [ neighbor ] = g_score [ neighbor ] + self:HeuristicCost ( neighbor, goal ) - - if self:not_in ( openset, neighbor ) then - table.insert ( openset, neighbor ) - end - - end - end - end - end - - return nil -- no valid path -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index b894e7621..7267403ac 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -740,19 +740,6 @@ function AIRWING:onafterStart(From, Event, To) -- Info. self:I(self.lid..string.format("Starting AIRWING v%s", AIRWING.version)) - -- Menu. - if false then - - -- Add F10 radio menu. - self:_SetMenuCoalition() - - for _,_squadron in pairs(self.squadrons) do - local squadron=_squadron --Ops.Squadron#SQUADRON - self:_AddSquadonMenu(squadron) - end - - end - end --- Update status. @@ -2162,140 +2149,6 @@ function AIRWING:GetMissionFromRequest(Request) return self:GetMissionFromRequestID(Request.uid) end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Menu Functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Patrol carrier. --- @param #AIRWING self --- @return #AIRWING self -function AIRWING:_SetMenuCoalition() - - -- Get coalition. - local Coalition=self:GetCoalition() - - -- Init menu table. - self.menu=self.menu or {} - - -- Init menu coalition table. - self.menu[Coalition]=self.menu[Coalition] or {} - - -- Shortcut. - local menu=self.menu[Coalition] - - if self.menusingle then - -- F10/Skipper/... - if not menu.AIRWING then - menu.AIRWING=MENU_COALITION:New(Coalition, "AIRWING") - end - else - -- F10/Skipper//... - if not menu.Root then - menu.Root=MENU_COALITION:New(Coalition, "AIRWING") - end - menu.AIRWING=MENU_COALITION:New(Coalition, self.alias, menu.Root) - end - - ------------------- - -- Squadron Menu -- - ------------------- - - menu.Squadron={} - menu.Squadron.Main= MENU_COALITION:New(Coalition, "Squadrons", menu.AIRWING) - - menu.Warehouse={} - menu.Warehouse.Main = MENU_COALITION:New(Coalition, "Warehouse", menu.AIRWING) - menu.Warehouse.Reports = MENU_COALITION_COMMAND:New(Coalition, "Reports On/Off", menu.Warehouse.Main, self.WarehouseReportsToggle, self) - menu.Warehouse.Assets = MENU_COALITION_COMMAND:New(Coalition, "Report Assets", menu.Warehouse.Main, self.ReportWarehouseStock, self) - - menu.ReportSquadrons = MENU_COALITION_COMMAND:New(Coalition, "Report Squadrons", menu.AIRWING, self.ReportSquadrons, self) - -end - ---- Report squadron status. --- @param #AIRWING self -function AIRWING:ReportSquadrons() - - local text="Squadron Report:" - - for i,_squadron in pairs(self.squadrons) do - local squadron=_squadron - - local name=squadron.name - - local nspawned=0 - local nstock=0 - for _,_asset in pairs(squadron.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - - local n=asset.nunits - - if asset.spawned then - nspawned=nspawned+n - else - nstock=nstock+n - end - - end - - text=string.format("\n%s: AC on duty=%d, in stock=%d", name, nspawned, nstock) - - end - - self:I(self.lid..text) - MESSAGE:New(text, 10, "AIRWING", true):ToCoalition(self:GetCoalition()) - -end - - ---- Add sub menu for this intruder. --- @param #AIRWING self --- @param Ops.Squadron#SQUADRON squadron The squadron data. -function AIRWING:_AddSquadonMenu(squadron) - - local Coalition=self:GetCoalition() - - local root=self.menu[Coalition].Squadron.Main - - local menu=MENU_COALITION:New(Coalition, squadron.name, root) - - MENU_COALITION_COMMAND:New(Coalition, "Report", menu, self._ReportSq, self, squadron) - MENU_COALITION_COMMAND:New(Coalition, "Launch CAP", menu, self._LaunchCAP, self, squadron) - - -- Set menu. - squadron.menu=menu - -end - - ---- Report squadron status. --- @param #AIRWING self --- @param Ops.Squadron#SQUADRON squadron The squadron object. -function AIRWING:_ReportSq(squadron) - - local text=string.format("%s: %s assets:", squadron.name, tostring(squadron.categoryname)) - for i,_asset in pairs(squadron.assets) do - local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem - text=text..string.format("%d.) ") - end -end - ---- Warehouse reports on/off. --- @param #AIRWING self -function AIRWING:WarehouseReportsToggle() - self.Report=not self.Report - MESSAGE:New(string.format("Warehouse reports are now %s", tostring(self.Report)), 10, "AIRWING", true):ToCoalition(self:GetCoalition()) -end - - ---- Report warehouse stock. --- @param #AIRWING self -function AIRWING:ReportWarehouseStock() - local text=self:_GetStockAssetsText(false) - MESSAGE:New(text, 10, "AIRWING", true):ToCoalition(self:GetCoalition()) -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index eb7cd5a29..c0b06bbfe 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -345,11 +345,10 @@ function CHIEF:onafterStart(From, Event, To) -- Start parent INTEL. self:GetParent(self).onafterStart(self, From, Event, To) - -- Start attached airwings. - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - if airwing:GetState()=="NotReadyYet" then - airwing:Start() + -- Start wingcommander. + if self.wingcommander then + if self.wingcommander:GetState()=="NotReadyYet" then + self.wingcommander:Start() end end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 4773499a7..26a2f0af3 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -480,7 +480,7 @@ function INTEL:CreateDetectedItems(detectedunitset) item.attribute=group:GetAttribute() item.category=group:GetCategory() item.categoryname=group:GetCategoryName() - item.threatlevel=group:GetUnit(1):GetThreatLevel() + item.threatlevel=group:GetThreatLevel() item.position=group:GetCoordinate() item.velocity=group:GetVelocityVec3() item.speed=group:GetVelocityMPS() @@ -699,7 +699,7 @@ function INTEL:PaintPicture() -- Update F10 marker. - self:UpdateClusterMarker(cluster, coordinate) + self:UpdateClusterMarker(cluster) end end @@ -959,7 +959,7 @@ end --- Update cluster F10 marker. -- @param #INTEL self -- @param #INTEL.Cluster cluster The cluster. --- @param Core.Point#COORDINATE newcoordinate Updated cluster positon. +-- @return #INTEL self function INTEL:UpdateClusterMarker(cluster, newcoordinate) -- Create a marker. @@ -976,10 +976,9 @@ function INTEL:UpdateClusterMarker(cluster, newcoordinate) refresh=true end - if newcoordinate then - cluster.coordinate=newcoordinate + if cluster.marker.coordinate~=cluster.coordinate then cluster.marker.coordinate=cluster.coordinate - refresh=true + refresh=true end if refresh then @@ -988,6 +987,7 @@ function INTEL:UpdateClusterMarker(cluster, newcoordinate) end + return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index bab3034c9..bd5a83106 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -19,7 +19,6 @@ -- @field #boolean turning If true, group is currently turning. -- @field #NAVYGROUP.IntoWind intowind Into wind info. -- @field #table Qintowind Queue of "into wind" turns. --- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @field #number depth Ordered depth in meters. -- @extends Ops.OpsGroup#OPSGROUP @@ -1230,7 +1229,7 @@ function NAVYGROUP:_CheckGroupDone(delay) else - self:UpdateRoute(nil, nil, self.depth) + self:__UpdateRoute(-1, nil, nil, self.depth) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 29845a1f5..237ab2316 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -26,7 +26,8 @@ -- @field #boolean isGround If true, group is some ground unit. -- @field #table waypoints Table of waypoints. -- @field #table waypoints0 Table of initial waypoints. --- @field #number currentwp Current waypoint. +-- @field #number currentwp Current waypoint index. This is the index of the last passed waypoint. +-- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @field #table taskqueue Queue of tasks. -- @field #number taskcounter Running number of task ids. -- @field #number taskcurrent ID of current task. If 0, there is no current task assigned. @@ -434,19 +435,30 @@ end --- Get next waypoint index. -- @param #OPSGROUP self --- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. +-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. Default is patrol ad infinitum value set. -- @return #number Next waypoint index. function OPSGROUP:GetWaypointIndexNext(cyclic) - local n=math.min(self.currentwp+1, #self.waypoints) + cyclic=cyclic or self.adinfinitum - if cyclic and self.currentwp==#self.waypoints then + local N=#self.waypoints + + local n=math.min(self.currentwp+1, N) + + if cyclic and self.currentwp==N then n=1 end return n end +--- Get current waypoint index. This is the index of the last passed waypoint. +-- @param #OPSGROUP self +-- @return #number Current waypoint index. +function OPSGROUP:GetWaypointIndexCurrent() + return self.currentwp or 1 +end + --- Get waypoint speed. -- @param #OPSGROUP self -- @param #number indx Waypoint index. @@ -465,14 +477,14 @@ end --- Get waypoint. -- @param #OPSGROUP self -- @param #number indx Waypoint index. --- @return #table Waypoint table. +-- @return #OPSGROUP.Waypoint Waypoint table. function OPSGROUP:GetWaypoint(indx) return self.waypoints[indx] end --- Get final waypoint. -- @param #OPSGROUP self --- @return #table Waypoint table. +-- @return #OPSGROUP.Waypoint Final waypoint table. function OPSGROUP:GetWaypointFinal() return self.waypoints[#self.waypoints] end @@ -480,7 +492,7 @@ end --- Get next waypoint. -- @param #OPSGROUP self -- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. --- @return #table Waypoint table. +-- @return #OPSGROUP.Waypoint Next waypoint table. function OPSGROUP:GetWaypointNext(cyclic) local n=self:GetWaypointIndexNext(cyclic) @@ -490,7 +502,7 @@ end --- Get current waypoint. -- @param #OPSGROUP self --- @return #table Waypoint table. +-- @return #OPSGROUP.Waypoint Current waypoint table. function OPSGROUP:GetWaypointCurrent() return self.waypoints[self.currentwp] end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index dd1da2d86..5b94d81aa 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -247,7 +247,7 @@ end --- Set number of units in groups. -- @param #SQUADRON self --- @param #nunits Number of units. Must be >=1 and <=4. Default 2. +-- @param #number nunits Number of units. Must be >=1 and <=4. Default 2. -- @return #SQUADRON self function SQUADRON:SetGrouping(nunits) self.ngrouping=nunits or 2 diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 98881138f..36a7ca95e 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2100,6 +2100,7 @@ end --- Calculate the maxium A2G threat level of the Group. -- @param #GROUP self +-- @return #number Number between 0 and 10. function GROUP:CalculateThreatLevelA2G() local MaxThreatLevelA2G = 0 @@ -2115,6 +2116,25 @@ function GROUP:CalculateThreatLevelA2G() return MaxThreatLevelA2G end +--- Get threat level of the group. +-- @param #GROUP self +-- @return #number Max threat level (a number between 0 and 10). +function GROUP:GetThreatLevel() + + local threatlevelMax = 0 + for UnitName, UnitData in pairs(self:GetUnits()) do + local ThreatUnit = UnitData -- Wrapper.Unit#UNIT + + local threatlevel = ThreatUnit:GetThreatLevel() + if threatlevel > threatlevelMax then + threatlevelMax=threatlevel + end + end + + return threatlevelMax +end + + --- Returns true if the first unit of the GROUP is in the air. -- @param Wrapper.Group#GROUP self -- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive. From 2328f9a36bcf25cb3bc406527c301451f435e2c6 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 23 Jul 2020 19:15:26 +0200 Subject: [PATCH 15/79] CHIEF --- Moose Development/Moose/Ops/AirWing.lua | 8 +++--- Moose Development/Moose/Ops/ChiefOfStaff.lua | 11 ++++++-- Moose Development/Moose/Ops/WingCommander.lua | 25 +++++++++++-------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 7267403ac..e87de00e1 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1555,8 +1555,8 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) end -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander then - self.wingcommander.detectionset:AddGroup(asset.flightgroup.group) + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:AddGroup(asset.flightgroup.group) end end @@ -1574,8 +1574,8 @@ function AIRWING:onafterAssetDead(From, Event, To, asset, request) self:GetParent(self).onafterAssetDead(self, From, Event, To, asset, request) -- Add group to the detection set of the WINGCOMMANDER. - if self.wingcommander then - self.wingcommander.detectionset:RemoveGroupsByName({asset.spawngroupname}) + if self.wingcommander and self.wingcommander.chief then + self.wingcommander.chief.detectionset:RemoveGroupsByName({asset.spawngroupname}) end -- Remove asset from mission is done via Mission:AssetDead() call from flightgroup onafterFlightDead function diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index c0b06bbfe..af72725a3 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -110,6 +110,7 @@ function CHIEF:New(AgentSet, Coalition) self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a GENERAL. self:AddTransition("*", "CancelMission", "*") -- Cancel mission. self:AddTransition("*", "Defcon", "*") -- Change defence condition. + self:AddTransition("*", "DeclareWar", "*") -- Declare War. ------------------------ --- Pseudo Functions --- @@ -238,12 +239,14 @@ end --- Set the wing commander for the airforce. -- @param #CHIEF self --- @param Ops.WingCommander WingCommander The WINGCOMMANDER object. +-- @param Ops.WingCommander#WINGCOMMANDER WingCommander The WINGCOMMANDER object. -- @return #CHIEF self function CHIEF:SetWingCommander(WingCommander) self.wingcommander=WingCommander + self.wingcommander.chief=self + return self end @@ -594,14 +597,18 @@ function CHIEF:CheckMissionQueue() -- PLANNNED Mission --- + -- Check if there is an airwing that can do the mission. local airwing=self:GetAirwingForMission(mission) if airwing then -- Add mission to airwing. - self:MissionAssign(airwing, mission) + self:AssignMissionAirforce(mission) return + + else + self:T(self.lid.."NO airwing") end else diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index b6ada11ae..13f38e89a 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -18,6 +18,7 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #table airwings Table of airwings which are commanded. -- @field #table missionqueue Mission queue. +-- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM --- Be surprised! @@ -65,12 +66,19 @@ function WINGCOMMANDER:New() -- Inherit everything from INTEL class. local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER + + self.lid="WINGCOMMANDER | " + + -- Start state. + self:SetStartState("NotReadyYet") -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING. - self:AddTransition("*", "CancelMission", "*") -- Cancel mission. - self:AddTransition("*", "Defcon", "*") -- Cancel mission. + -- From State --> Event --> To State + self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. + self:AddTransition("*", "Status", "*") -- Status report. + self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING. + self:AddTransition("*", "CancelMission", "*") -- Cancel mission. + self:AddTransition("*", "Defcon", "*") -- Cancel mission. ------------------------ --- Pseudo Functions --- @@ -112,7 +120,6 @@ function WINGCOMMANDER:New() end self.Debug=true - return self end @@ -182,9 +189,6 @@ function WINGCOMMANDER:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting Wing Commander") self:I(self.lid..text) - - -- Start parent INTEL. - self:GetParent(self).onafterStart(self, From, Event, To) -- Start attached airwings. for _,_airwing in pairs(self.airwings) do @@ -194,6 +198,7 @@ function WINGCOMMANDER:onafterStart(From, Event, To) end end + self:__Status(-1) end --- On after "Status" event. @@ -204,9 +209,6 @@ end -- @param #string To To state. function WINGCOMMANDER:onafterStatus(From, Event, To) - -- Start parent INTEL. - self:GetParent(self).onafterStatus(self, From, Event, To) - -- FSM state. local fsmstate=self:GetState() @@ -226,6 +228,7 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) self:I(self.lid..text) end + self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 04923b65b2278ce157a53114f76e6a783affca5b Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 24 Jul 2020 01:34:38 +0200 Subject: [PATCH 16/79] Stuff --- Moose Development/Moose/Core/Astar.lua | 92 +++++++++++++++----- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/ArmyGroup.lua | 20 ++--- Moose Development/Moose/Ops/Auftrag.lua | 40 +++++---- Moose Development/Moose/Ops/ChiefOfStaff.lua | 18 +++- 5 files changed, 114 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 6650e15dc..58459db13 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -78,8 +78,6 @@ function ASTAR:New() local self=BASE:Inherit(self, BASE:New()) --#ASTAR self.lid="ASTAR | " - - self.Debug=true return self end @@ -136,6 +134,19 @@ function ASTAR:AddNode(Node) return self end +--- Add a node to the table of grid nodes specifying its coordinate. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created. +-- @return #ASTAR self +function ASTAR:AddNodeFromCoordinate(Coordinate) + + local node=self:GetNodeFromCoordinate(Coordinate) + + self:AddNode(node) + + return self +end + --- Check if the coordinate of a node has is at a valid surface type. -- @param #ASTAR self -- @param #ASTAR.Node Node The node to be added. @@ -163,17 +174,6 @@ function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes) end ---- Set valid neighbours to require line of sight between two nodes. --- @param #ASTAR self --- @param #number CorridorWidth Width of LoS corridor in meters. --- @return #ASTAR self -function ASTAR:SetValidNeighbourLoS(CorridorWidth) - - self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth) - - return self -end - --- Add a function to determine if a neighbour of a node is valid. -- @param #ASTAR self -- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid. @@ -191,6 +191,32 @@ function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...) return self end + +--- Set valid neighbours to require line of sight between two nodes. +-- @param #ASTAR self +-- @param #number CorridorWidth Width of LoS corridor in meters. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourLoS(CorridorWidth) + + self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth) + + return self +end + +--- Set valid neighbours to be in a certain distance. +-- @param #ASTAR self +-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourDistance(MaxDistance) + + self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance) + + return self +end + + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Grid functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -329,6 +355,20 @@ function ASTAR.LoS(nodeA, nodeB, corridor) return los end +--- Function to check if two nodes have line of sight (LoS). +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @param #number distmax Max distance in meters. Default is 2000 m. +-- @return #boolean If true, distance between the two nodes is below threshold. +function ASTAR.DistMax(nodeA, nodeB, distmax) + + distmax=distmax or 2000 + + local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate) + + return dist<=distmax +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -338,6 +378,7 @@ end -- @param #ASTAR self -- @param Core.Point#COORDINATE Coordinate. -- @return #ASTAR.Node Cloest node to the coordinate. +-- @return #number Distance to closest node in meters. function ASTAR:FindClosestNode(Coordinate) local distMin=math.huge @@ -355,16 +396,22 @@ function ASTAR:FindClosestNode(Coordinate) end - return closeNode + return closeNode, distMin end ---- Add a node. +--- Find the start node. -- @param #ASTAR self -- @param #ASTAR.Node Node The node to be added to the nodes table. -- @return #ASTAR self function ASTAR:FindStartNode() - self.startNode=self:FindClosestNode(self.startCoord) + local node, dist=self:FindClosestNode(self.startCoord) + + self.startNode=node + + if dist>1000 then + self:AddNode(node) + end return self end @@ -375,7 +422,13 @@ end -- @return #ASTAR self function ASTAR:FindEndNode() - self.endNode=self:FindClosestNode(self.endCoord) + local node, dist=self:FindClosestNode(self.endCoord) + + self.endNode=node + + if dist>1000 then + self:AddNode(node) + end return self end @@ -432,9 +485,6 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) table.remove(path, 1) end - -- Set end time. - local T9=timer.getAbsTime() - -- Debug message. local text=string.format("Found path with %d nodes", #path) self:I(self.lid..text) @@ -473,7 +523,7 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) -- Debug message. local text=string.format("WARNING: Could NOT find valid path!") - self:I(self.lid..text) + self:E(self.lid..text) MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) return nil -- no valid path diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index f4a6ab8b6..d13ac38b5 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -75,6 +75,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Auftrag.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 0533311fc..86171b2af 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -70,6 +70,7 @@ function ARMYGROUP:New(GroupName) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("*", "FullStop", "Holding") -- Hold position. + self:AddTransition("*", "Cruise", "Cruising") -- Hold position. self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. @@ -342,11 +343,6 @@ function ARMYGROUP:onafterSpawned(From, Event, To) end - -- Get orientation. - self.Corientlast=self.group:GetUnit(1):GetOrientationX() - - self.depth=self.group:GetHeight() - -- Update route. self:Cruise() @@ -462,7 +458,7 @@ function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, Resu local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() -- Current waypoint. - local current=self:GetCoordinate():WaypointGround(Speed,Formation,DCSTasks) + local current=self:GetCoordinate():WaypointGround(Speed, Formation, DCSTasks) table.insert(waypoints, current) -- At each waypoint report passing. @@ -534,8 +530,6 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed) end - - --- On after "Dead" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -624,7 +618,7 @@ function ARMYGROUP:OnEventBirth(EventData) end ---- Flightgroup event function handling the crash of a unit. +--- Event function handling the crash of a unit. -- @param #ARMYGROUP self -- @param Core.Event#EVENTDATA EventData Event data. function ARMYGROUP:OnEventDead(EventData) @@ -649,7 +643,7 @@ function ARMYGROUP:OnEventDead(EventData) end ---- Flightgroup event function handling the crash of a unit. +--- Event function handling the crash of a unit. -- @param #ARMYGROUP self -- @param Core.Event#EVENTDATA EventData Event data. function ARMYGROUP:OnEventRemoveUnit(EventData) @@ -755,9 +749,7 @@ function ARMYGROUP:_InitGroup() self.position=self:GetCoordinate() -- Radio parameters from template. - self.radioOn=true -- Radio is always on for ships. - self.radioFreq=tonumber(self.template.units[1].frequency)/1000000 - self.radioModu=tonumber(self.template.units[1].modulation)/1000000 + self.radioOn=false -- Radio is always OFF for ground. -- If not set by the use explicitly yet, we take the template values as defaults. if not self.radioFreqDefault then @@ -810,7 +802,7 @@ function ARMYGROUP:_InitGroup() --text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) + --text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) --text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 2501b6693..143b0f9e1 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1817,13 +1817,15 @@ function AUFTRAG:onafterStatus(From, Event, To) self:E(self.lid..string.format("ERROR: FSM state %s != %s mission status!", fsmstate, self.status)) end + -- Data on assigned groups. local text="Group data:" for groupname,_groupdata in pairs(self.groupdata) do local groupdata=_groupdata --#AUFTRAG.GroupData text=text..string.format("\n- %s: status mission=%s opsgroup=%s", groupname, groupdata.status, groupdata.opsgroup and groupdata.opsgroup:GetState() or "N/A") end - self:I(self.lid..text) + self:T(self.lid..text) + -- Ready to evaluate mission outcome? local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false -- Check if mission is OVER (done or cancelled) and enough time passed to evaluate the result. @@ -2014,7 +2016,7 @@ end -- @param Ops.OpsGroup#OPSGROUP opsgroup The flight group. -- @param Ops.OpsGroup#OPSGROUP.Task task Waypoint task. function AUFTRAG:SetGroupWaypointTask(opsgroup, task) - self:I(self.lid..string.format("Setting waypoint task %s", task and task.description or "WTF")) + self:T2(self.lid..string.format("Setting waypoint task %s", task and task.description or "WTF")) local groupdata=self:GetGroupData(opsgroup) if groupdata then groupdata.waypointtask=task @@ -2037,7 +2039,7 @@ end -- @param Ops.OpsGroup#OPSGROUP opsgroup The flight group. -- @param #number waypointindex Waypoint index. function AUFTRAG:SetGroupWaypointIndex(opsgroup, waypointindex) - self:I(self.lid..string.format("Setting waypoint index %d", waypointindex)) + self:T2(self.lid..string.format("Setting waypoint index %d", waypointindex)) local groupdata=self:GetGroupData(opsgroup) if groupdata then groupdata.waypointindex=waypointindex @@ -2127,7 +2129,7 @@ end function AUFTRAG:onafterQueued(From, Event, To, Airwing) self.status=AUFTRAG.Status.QUEUED self.airwing=Airwing - self:I(self.lid..string.format("New mission status=%s at airwing %s", self.status, tostring(Airwing.alias))) + self:T(self.lid..string.format("New mission status=%s at airwing %s", self.status, tostring(Airwing.alias))) end @@ -2138,7 +2140,7 @@ end -- @param #string To To state. function AUFTRAG:onafterRequested(From, Event, To) self.status=AUFTRAG.Status.REQUESTED - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) end --- On after "Assign" event. @@ -2148,7 +2150,7 @@ end -- @param #string To To state. function AUFTRAG:onafterAssign(From, Event, To) self.status=AUFTRAG.Status.ASSIGNED - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) end --- On after "Schedule" event. Mission is added to the mission queue of a FLIGHTGROUP. @@ -2159,7 +2161,7 @@ end -- @param Ops.OpsGroup#OPSGROUP FlightGroup function AUFTRAG:onafterScheduled(From, Event, To, FlightGroup) self.status=AUFTRAG.Status.SCHEDULED - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) end --- On after "Start" event. @@ -2169,7 +2171,7 @@ end -- @param #string To To state. function AUFTRAG:onafterStarted(From, Event, To) self.status=AUFTRAG.Status.STARTED - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) end --- On after "Execute" event. @@ -2179,7 +2181,7 @@ end -- @param #string To To state. function AUFTRAG:onafterExecuting(From, Event, To) self.status=AUFTRAG.Status.EXECUTING - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) end --- On after "Done" event. @@ -2189,7 +2191,7 @@ end -- @param #string To To state. function AUFTRAG:onafterDone(From, Event, To) self.status=AUFTRAG.Status.DONE - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) -- Set time stamp. self.Tover=timer.getAbsTime() @@ -2259,7 +2261,7 @@ end function AUFTRAG:onafterSuccess(From, Event, To) self.status=AUFTRAG.Status.SUCCESS - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) -- Stop mission. self:Stop() @@ -2287,20 +2289,20 @@ function AUFTRAG:onafterCancel(From, Event, To) if self.wingcommander then - self:I(self.lid..string.format("Wingcommander will cancel the mission. Will wait for mission DONE before evaluation!")) + self:T(self.lid..string.format("Wingcommander will cancel the mission. Will wait for mission DONE before evaluation!")) self.wingcommander:CancelMission(self) elseif self.airwing then - self:I(self.lid..string.format("Airwing %s will cancel the mission. Will wait for mission DONE before evaluation!", self.airwing.alias)) + self:T(self.lid..string.format("Airwing %s will cancel the mission. Will wait for mission DONE before evaluation!", self.airwing.alias)) -- Airwing will cancel all flight missions and remove queued request from warehouse queue. self.airwing:MissionCancel(self) else - self:I(self.lid..string.format("No airwing or wingcommander. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) + self:T(self.lid..string.format("No airwing or wingcommander. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!")) for _,_groupdata in pairs(self.groupdata) do local groupdata=_groupdata --#AUFTRAG.GroupData @@ -2311,7 +2313,7 @@ function AUFTRAG:onafterCancel(From, Event, To) -- Special mission states. if self.status==AUFTRAG.Status.PLANNED then - self:I(self.lid..string.format("Cancelled mission was in planned stage. Call it done!")) + self:T(self.lid..string.format("Cancelled mission was in planned stage. Call it done!")) self:Done() end @@ -2325,7 +2327,7 @@ end function AUFTRAG:onafterFailed(From, Event, To) self.status=AUFTRAG.Status.FAILED - self:I(self.lid..string.format("New mission status=%s", self.status)) + self:T(self.lid..string.format("New mission status=%s", self.status)) if self.missionRepeated>=self.missionRepeatMax then @@ -2353,7 +2355,7 @@ function AUFTRAG:onafterRepeat(From, Event, To) -- Set mission status to PLANNED. self.status=AUFTRAG.Status.PLANNED - self:I(self.lid..string.format("New mission status=%s (on Repeat)", self.status)) + self:T(self.lid..string.format("New mission status=%s (on Repeat)", self.status)) -- Increase repeat counter. self.missionRepeated=self.missionRepeated+1 @@ -2452,7 +2454,7 @@ function AUFTRAG:DelAsset(Asset) local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset if asset.uid==Asset.uid then - self:I(self.lid..string.format("Removing asset \"%s\" from mission", tostring(asset.spawngroupname))) + self:T(self.lid..string.format("Removing asset \"%s\" from mission", tostring(asset.spawngroupname))) table.remove(self.assets, i) return self end @@ -3289,7 +3291,7 @@ function AUFTRAG:_TargetFromObject(Object) self.Ntargets=Ninitial -- Debug info. - self:I(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", target.Name, target.Type, Ninitial, Lifepoints)) + self:T(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", target.Name, target.Type, Ninitial, Lifepoints)) return target end diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index af72725a3..682872dbc 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -374,7 +374,7 @@ function CHIEF:onafterStatus(From, Event, To) -- Clean up missions where the contact was lost. for _,_contact in pairs(self.ContactsLost) do - local contact=_contact --#CHIEF.Contact + local contact=_contact --#INTEL.Contact if contact.mission and contact.mission:IsNotOver() then @@ -539,7 +539,6 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #string Defcon New defence condition. --- @param Ops.Auftrag#AUFTRAG Mission The mission. function CHIEF:onbeforeDefcon(From, Event, To, Defcon) local gotit=false @@ -569,7 +568,6 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #string Defcon New defence condition. --- @param Ops.Auftrag#AUFTRAG Mission The mission. function CHIEF:onafterDefcon(From, Event, To, Defcon) self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) @@ -577,6 +575,20 @@ function CHIEF:onafterDefcon(From, Event, To, Defcon) self.Defcon=Defcon end +--- On after "DeclareWar" event. +-- @param #CHIEF self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #CHIEF Chief The Chief we declared war on. +function CHIEF:onafterDeclareWar(From, Event, To, Chief) + + if Chief then + self:AddWarOnChief(Chief) + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Resources ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 2f6d9b640fc4221bdf40e066ce44dda79db38e0e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 29 Jul 2020 01:10:13 +0200 Subject: [PATCH 17/79] NAVY --- Moose Development/Moose/Core/Astar.lua | 237 ++++++++-- Moose Development/Moose/Core/Point.lua | 32 +- Moose Development/Moose/Ops/ArmyGroup.lua | 131 +++--- Moose Development/Moose/Ops/FlightGroup.lua | 2 + Moose Development/Moose/Ops/NavyGroup.lua | 419 +++++++++++++---- Moose Development/Moose/Ops/OpsGroup.lua | 476 +++++++++++++------- Moose Development/Moose/Utilities/Enums.lua | 9 + 7 files changed, 954 insertions(+), 352 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 58459db13..f4107f4f9 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -2,13 +2,16 @@ -- -- **Main Features:** -- --- * Stuff +-- * Find path from A to B. +-- * Pre-defined as well as custom valid neighbour functions. +-- * Pre-defined as well as custom cost functions. +-- * Easy rectangular grid setup. -- -- === -- -- ### Author: **funkyfranky** -- @module Core.Astar --- @image CORE_Atar.png +-- @image CORE_Astar.png --- ASTAR class. @@ -21,20 +24,110 @@ -- @field #ASTAR.Node endNode End node. -- @field Core.Point#COORDINATE startCoord Start coordinate. -- @field Core.Point#COORDINATE endCoord End coordinate. --- @field #func ValidNeighbourFunc Function to check if a node is valid. +-- @field #function ValidNeighbourFunc Function to check if a node is valid. -- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function. +-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another. +-- @field #table CostArg Optional arguments passed to the cost function. -- @extends Core.Base#BASE --- When nothing goes right... Go left! -- -- === -- --- ![Banner Image](..\Presentations\WingCommander\ASTAR_Main.jpg) +-- ![Banner Image](..\Presentations\Astar\ASTAR_Main.jpg) -- -- # The ASTAR Concept -- -- Pathfinding algorithm. -- +-- +-- # Start and Goal +-- +-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually. +-- +-- ## Start +-- +-- The start +-- +-- ## Goal +-- +-- +-- # Nodes +-- +-- ## Rectangular Grid +-- +-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where +-- +-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid. +-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km). +-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km). +-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km). +-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*. +-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created. +-- +-- ## Valid Surfaces +-- +-- Certain unit types can only travel on certain surfaces types, for example +-- +-- * Naval units can only travel on water (that also excludes shallow water in DCS currently), +-- * Ground units can only traval on land. +-- +-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient. +-- +-- ## Box Width (BoxHY) +-- +-- The box width needs to be large enough to capture all paths you want to consider. +-- +-- ## Space in X +-- +-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node. +-- +-- ## Grid Spacing +-- +-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible. +-- However, if the value is too large, the algorithm might fail to get a valid path. +-- +-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path. +-- +-- # Valid Neighbours +-- +-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible. +-- +-- ## Line of Sight +-- +-- For naval +-- +-- +-- # Heuristic Cost +-- +-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another. +-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes. +-- +-- +-- # Calculate the Path +-- +-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which +-- describe the optimal path from the start node to the end node. +-- +-- By default, the start and end node are include in the table that is returned. +-- +-- Note that a valid path must not always exist. So you should check if the function returns *nil*. +-- +-- Common reasons that a path cannot be found are: +-- +-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid. +-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY* +-- * There simply is no valid path ==> you are screwed :( +-- +-- +-- # Examples +-- +-- ## Strait of Hormuz +-- +-- Carrier Group finds its way through the Stait of Hormuz. +-- +-- ## +-- -- -- -- @field #ASTAR @@ -45,13 +138,13 @@ ASTAR = { nodes = {}, } ---- Defence condition. +--- Node data. -- @type ASTAR.Node -- @field Core.Point#COORDINATE coordinate Coordinate of the node. -- @field #number surfacetype Surface type. ---- ASTAR infinity --- @field #string INF +--- ASTAR infinity. +-- @field #number INF ASTAR.INF=1/0 --- ASTAR class version. @@ -137,14 +230,14 @@ end --- Add a node to the table of grid nodes specifying its coordinate. -- @param #ASTAR self -- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created. --- @return #ASTAR self +-- @return #ASTAR.Node The node. function ASTAR:AddNodeFromCoordinate(Coordinate) local node=self:GetNodeFromCoordinate(Coordinate) self:AddNode(node) - return self + return node end --- Check if the coordinate of a node has is at a valid surface type. @@ -214,8 +307,44 @@ function ASTAR:SetValidNeighbourDistance(MaxDistance) return self end +--- Set the function which calculates the "cost" to go from one to another node. +-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments. +-- Very often the distance between nodes is a good measure for the cost. +-- @param #ASTAR self +-- @param #function CostFunction Function that returns the "cost". +-- @param ... Condition function arguments if any. +-- @return #ASTAR self +function ASTAR:SetCostFunction(CostFunction, ...) + self.CostFunc=CostFunction + + self.CostArg={} + if arg then + self.CostArg=arg + end + + return self +end +--- Set heuristic cost to go from one node to another to be their 2D distance. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:SetCostDist2D() + + self:SetCostFunction(ASTAR.Dist2D) + + return self +end + +--- Set heuristic cost to go from one node to another to be their 3D distance. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:SetCostDist3D() + + self:SetCostFunction(ASTAR.Dist3D) + + return self +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Grid functions @@ -369,6 +498,26 @@ function ASTAR.DistMax(nodeA, nodeB, distmax) return dist<=distmax end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Heuristic cost functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Heuristic cost is given by the 2D distance between the nodes. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #number Distance between the two nodes. +function ASTAR.Dist2D(nodeA, nodeB) + return nodeA.coordinate:Get2DDistance(nodeB) +end + +--- Heuristic cost is given by the 3D distance between the nodes. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #number Distance between the two nodes. +function ASTAR.Dist3D(nodeA, nodeB) + return nodeA.coordinate:Get3DDistance(nodeB.coordinate) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -458,7 +607,7 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) local g_score, f_score = {}, {} g_score[start]=0 - f_score[start]=g_score[start]+self:HeuristicCost(start, goal) + f_score[start]=g_score[start]+self:_HeuristicCost(start, goal) -- Set start time. local T0=timer.getAbsTime() @@ -470,12 +619,12 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) while #openset > 0 do - local current=self:LowestFscore(openset, f_score) + local current=self:_LowestFscore(openset, f_score) -- Check if we are at the end node. if current==goal then - local path=self:UnwindPath({}, came_from, goal) + local path=self:_UnwindPath({}, came_from, goal) if not ExcludeEndNode then table.insert(path, goal) @@ -493,26 +642,26 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) return path end - self:RemoveNode(openset, current) + self:_RemoveNode(openset, current) table.insert(closedset, current) - local neighbors=self:NeighbourNodes(current, nodes) + local neighbors=self:_NeighbourNodes(current, nodes) -- Loop over neighbours. for _,neighbor in ipairs(neighbors) do - if self:NotIn(closedset, neighbor) then + if self:_NotIn(closedset, neighbor) then - local tentative_g_score=g_score[current]+self:DistNodes(current, neighbor) + local tentative_g_score=g_score[current]+self:_DistNodes(current, neighbor) - if self:NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then + if self:_NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then came_from[neighbor]=current g_score[neighbor]=tentative_g_score - f_score[neighbor]=g_score[neighbor]+self:HeuristicCost(neighbor, goal) + f_score[neighbor]=g_score[neighbor]+self:_HeuristicCost(neighbor, goal) - if self:NotIn(openset, neighbor) then + if self:_NotIn(openset, neighbor) then table.insert(openset, neighbor) end @@ -533,22 +682,18 @@ end -- A* pathfinding helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Calculate 2D distance between two nodes. +--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes. -- @param #ASTAR self -- @param #ASTAR.Node nodeA Node A. -- @param #ASTAR.Node nodeB Node B. --- @return #number Distance between nodes in meters. -function ASTAR:DistNodes(nodeA, nodeB) - return nodeA.coordinate:Get2DDistance(nodeB.coordinate) -end +-- @return #number "Cost" to go from node A to node B. +function ASTAR:_HeuristicCost(nodeA, nodeB) ---- Heuristic cost function to go from node A to node B. That is simply the distance here. --- @param #ASTAR self --- @param #ASTAR.Node nodeA Node A. --- @param #ASTAR.Node nodeB Node B. --- @return #number Distance between nodes in meters. -function ASTAR:HeuristicCost(nodeA, nodeB) - return self:DistNodes(nodeA, nodeB) + if self.CostFunc then + return self.CostFunc(nodeA, nodeB, unpack(self.CostArg)) + else + return self:_DistNodes(nodeA, nodeB) + end end --- Check if going from a node to a neighbour is possible. @@ -556,7 +701,7 @@ end -- @param #ASTAR.Node node A node. -- @param #ASTAR.Node neighbor Neighbour node. -- @return #boolean If true, transition between nodes is possible. -function ASTAR:IsValidNeighbour(node, neighbor) +function ASTAR:_IsValidNeighbour(node, neighbor) if self.ValidNeighbourFunc then @@ -568,9 +713,21 @@ function ASTAR:IsValidNeighbour(node, neighbor) end ---- Function +--- Calculate 2D distance between two nodes. -- @param #ASTAR self -function ASTAR:LowestFscore(set, f_score) +-- @param #ASTAR.Node nodeA Node A. +-- @param #ASTAR.Node nodeB Node B. +-- @return #number Distance between nodes in meters. +function ASTAR:_DistNodes(nodeA, nodeB) + return nodeA.coordinate:Get2DDistance(nodeB.coordinate) +end + +--- Function that calculates the lowest F score. +-- @param #ASTAR self +-- @param #table set The set of nodes. +-- @param #number f_score F score. +-- @return #ASTAR.Node Best node. +function ASTAR:_LowestFscore(set, f_score) local lowest, bestNode = ASTAR.INF, nil @@ -591,14 +748,14 @@ end -- @param #ASTAR.Node theNode The node. -- @param #table nodes Possible neighbours. -- @param #table Valid neighbour nodes. -function ASTAR:NeighbourNodes(theNode, nodes) +function ASTAR:_NeighbourNodes(theNode, nodes) local neighbors = {} for _, node in ipairs ( nodes ) do if theNode~=node then - local isvalid=self:IsValidNeighbour(theNode, node) + local isvalid=self:_IsValidNeighbour(theNode, node) if isvalid then table.insert(neighbors, node) @@ -616,7 +773,7 @@ end -- @param #table set Set of nodes. -- @param #ASTAR.Node theNode The node to check. -- @return #boolean If true, the node is not in the set. -function ASTAR:NotIn(set, theNode) +function ASTAR:_NotIn(set, theNode) for _, node in ipairs ( set ) do if node == theNode then @@ -631,7 +788,7 @@ end -- @param #ASTAR self -- @param #table set Set of nodes. -- @param #ASTAR.Node theNode The node to check. -function ASTAR:RemoveNode(set, theNode) +function ASTAR:_RemoveNode(set, theNode) for i, node in ipairs ( set ) do if node == theNode then @@ -649,11 +806,11 @@ end -- @param #table map Map. -- @param #ASTAR.Node current_node The current node. -- @return #table Unwinded path. -function ASTAR:UnwindPath( flat_path, map, current_node ) +function ASTAR:_UnwindPath( flat_path, map, current_node ) if map [ current_node ] then table.insert ( flat_path, 1, map [ current_node ] ) - return self:UnwindPath ( flat_path, map, map [ current_node ] ) + return self:_UnwindPath ( flat_path, map, map [ current_node ] ) else return flat_path end diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 4b7b9a7ef..2fe7a3fa2 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -175,10 +175,6 @@ do -- COORDINATE -- In order to use the most optimal road system to transport vehicles, the method @{#COORDINATE.GetPathOnRoad}() will calculate -- the most optimal path following the road between two coordinates. -- - -- - -- - -- - -- -- ## 8) Metric or imperial system -- -- * @{#COORDINATE.IsMetric}(): Returns if the 3D point is Metric or Nautical Miles. @@ -204,23 +200,23 @@ do -- COORDINATE --- @field COORDINATE.WaypointAction COORDINATE.WaypointAction = { - TurningPoint = "Turning Point", - FlyoverPoint = "Fly Over Point", - FromParkingArea = "From Parking Area", + TurningPoint = "Turning Point", + FlyoverPoint = "Fly Over Point", + FromParkingArea = "From Parking Area", FromParkingAreaHot = "From Parking Area Hot", - FromRunway = "From Runway", - Landing = "Landing", - LandingReFuAr = "LandingReFuAr", + FromRunway = "From Runway", + Landing = "Landing", + LandingReFuAr = "LandingReFuAr", } --- @field COORDINATE.WaypointType COORDINATE.WaypointType = { - TakeOffParking = "TakeOffParking", + TakeOffParking = "TakeOffParking", TakeOffParkingHot = "TakeOffParkingHot", - TakeOff = "TakeOffParkingHot", - TurningPoint = "Turning Point", - Land = "Land", - LandingReFuAr = "LandingReFuAr", + TakeOff = "TakeOffParkingHot", + TurningPoint = "Turning Point", + Land = "Land", + LandingReFuAr = "LandingReFuAr", } @@ -1290,8 +1286,10 @@ do -- COORDINATE RoutePoint.x = self.x RoutePoint.y = self.z - RoutePoint.alt = self:GetLandHeight()+1 -- self.y + RoutePoint.alt = self:GetLandHeight()+1 RoutePoint.alt_type = COORDINATE.WaypointAltType.BARO + + RoutePoint.type = "Turning Point" RoutePoint.action = Formation or "Off Road" RoutePoint.formation_template="" @@ -1921,7 +1919,7 @@ do -- COORDINATE --- Returns if a Coordinate has Line of Sight (LOS) with the ToCoordinate. -- @param #COORDINATE self -- @param #COORDINATE ToCoordinate - -- @param #number OFfset Height offset in meters. Default 2 m. + -- @param #number Offset Height offset in meters. Default 2 m. -- @return #boolean true If the ToCoordinate has LOS with the Coordinate, otherwise false. function COORDINATE:IsLOS( ToCoordinate, Offset ) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 86171b2af..6e9cba673 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -16,7 +16,7 @@ -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @extends Ops.OpsGroup#OPSGROUP ---- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson +--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge -- -- === -- @@ -64,8 +64,9 @@ function ARMYGROUP:New(GroupName) -- Defaults self:SetDefaultROE() + self:SetDefaultAlarmstate() self:SetDetection() - self:SetPatrolAdInfinitum(true) + self:SetPatrolAdInfinitum(false) -- Add FSM transitions. -- From State --> Event --> To State @@ -134,7 +135,7 @@ function ARMYGROUP:SetPatrolAdInfinitum(switch) return self end ---- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. +--- Set default cruise speed. This is the speed a group will take by default if no speed is specified explicitly. -- @param #ARMYGROUP self -- @param #number Speed Speed in knots. Default 70% of max speed. -- @return #ARMYGROUP self @@ -225,8 +226,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) local nMissions=self:CountRemainingMissison() -- Info text. - local text=string.format("State %s: Wp=%d/%d Speed=%.1f Heading=%03d Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, speed, hdg, nTaskTot, nMissions) + local text=string.format("State %s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", + fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed), hdg, self.roe, self.alarmstate, self.formation, nTaskTot, nMissions) self:I(self.lid..text) else @@ -338,9 +339,12 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE and ROT options. + -- Set default ROE option. self:SetOptionROE(self.roe) + -- Set default Alarm State option. + self:SetOptionAlarmstate(self.alarmstate) + end -- Update route. @@ -355,38 +359,33 @@ end -- @param #string To To state. -- @param #number n Waypoint number. Default is next waypoint. -- @param #number Speed Speed in knots. Default cruise speed. --- @param #number Depth Depth in meters. Default 0 meters. -function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) +-- @param #number Formation Formation of the group. +function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) -- Debug info. - self:T(self.lid..string.format("FF Update route n=%d", n)) + self:I(self.lid..string.format("FF Update route n=%d", n)) -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. self:_UpdateWaypointTasks(n) -- Waypoints. local waypoints={} - - -- Depth for submarines. - local depth=Depth or 0 - -- Get current speed in km/h. - local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() - - -- Current waypoint. - local current=self:GetCoordinate():WaypointNaval(speed, depth) - table.insert(waypoints, current) - -- Add remaining waypoints to route. for i=n, #self.waypoints do - local wp=self.waypoints[i] + + -- Copy waypoint. + local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint - -- Set speed. if i==n then + --- + -- Next Waypoint + --- + if Speed then wp.speed=UTILS.KnotsToMps(Speed) elseif self.speedCruise then @@ -394,8 +393,22 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) else -- Take default waypoint speed. end + + if Formation then + wp.action=Formation + end + + -- Current set formation. + self.formation=wp.action + + -- Current set speed in m/s. + self.speed=wp.speed else + + --- + -- Later Waypoint(s) + --- if self.speedCruise then wp.speed=UTILS.KmphToMps(self.speedCruise) @@ -405,17 +418,23 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) end - -- Set depth. - wp.alt=-depth --Depth and -Depth or wp.alt - + -- Debug info. + self:I(string.format("WP %d %s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.type, wp.speed, wp.alt, wp.action)) + -- Add waypoint. table.insert(waypoints, wp) end + + -- Current waypoint. + local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speed), self.formation) + table.insert(waypoints, 1, current) + table.insert(waypoints, 1, current) -- Seems to be better to add this twice. Otherwise, the passing waypoint functions is triggered to early! + + if #waypoints>2 then - if #waypoints>1 then - - self:I(self.lid..string.format("Updateing route: WP=%d, Speed=%.1f knots, depth=%d meters", #self.waypoints-n+1, UTILS.KmphToKnots(speed), depth)) + self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Formation=%s", + self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speed), tostring(self.formation))) -- Route group to all defined waypoints remaining. self:Route(waypoints) @@ -428,11 +447,6 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) self:I(self.lid..string.format("No waypoints left")) - if #self.waypoints>1 then - self:I(self.lid..string.format("Resuming route at first waypoint")) - self:__UpdateRoute(-1, 1, nil, self.depth) - end - end end @@ -444,27 +458,24 @@ end -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate Coordinate where to go. -- @param #number Speed Speed in knots. Default cruise speed. --- @param #number Depth Depth in meters. Default 0 meters. +-- @param #number Formation Formation the group will use. -- @param #number ResumeRoute If true, resume route after detour point was reached. -function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute) +function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, ResumeRoute) -- Waypoints. local waypoints={} - -- Depth for submarines. - local depth=Depth or 0 - -- Get current speed in km/h. local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() -- Current waypoint. - local current=self:GetCoordinate():WaypointGround(Speed, Formation, DCSTasks) + local current=self:GetCoordinate():WaypointGround(Speed, Formation) table.insert(waypoints, current) -- At each waypoint report passing. local Task=self.group:TaskFunction("ARMYGROUP._DetourReached", self, ResumeRoute) - local detour=Coordinate:WaypointNaval(speed, depth, {Task}) + local detour=Coordinate:WaypointGround(Speed, Formation, {Task}) table.insert(waypoints, detour) self:Route(waypoints) @@ -482,21 +493,21 @@ end --- Function called when a group is passing a waypoint. --@param Wrapper.Group#GROUP group Group that passed the waypoint ---@param #ARMYGROUP navygroup Navy group object. +--@param #ARMYGROUP armygroup Army group object. --@param #boolean resume Resume route. -function ARMYGROUP._DetourReached(group, navygroup, resume) +function ARMYGROUP._DetourReached(group, armygroup, resume) -- Debug message. local text=string.format("Group reached detour coordinate") - navygroup:I(navygroup.lid..text) + armygroup:I(armygroup.lid..text) if resume then - local indx=navygroup:GetWaypointIndexNext(true) - local speed=navygroup:GetSpeedToWaypoint(indx) - navygroup:UpdateRoute(indx, speed, navygroup.depth) + local indx=armygroup:GetWaypointIndexNext(true) + local speed=armygroup:GetSpeedToWaypoint(indx) + armygroup:__UpdateRoute(-1, indx, speed, armygroup.formation) end - navygroup:DetourReached() + armygroup:DetourReached() end @@ -511,7 +522,7 @@ function ARMYGROUP:onafterFullStop(From, Event, To) local pos=self:GetCoordinate() -- Create a new waypoint. - local wp=pos:WaypointNaval(0) + local wp=pos:WaypointGround(0) -- Create new route consisting of only this position ==> Stop! self:Route({wp}) @@ -524,9 +535,10 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number Speed Speed in knots. -function ARMYGROUP:onafterCruise(From, Event, To, Speed) +-- @param #number Formation Formation. +function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) - self:UpdateRoute(nil, Speed, self.depth) + self:__UpdateRoute(-1, nil, Speed, Formation) end @@ -675,9 +687,10 @@ end -- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. -- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number wpnumber Waypoint number. Default at the end. +-- @param #number formation Formation the group will use. -- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. -- @return #number Waypoint index. -function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) +function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, formation, updateroute) -- Waypoint number. Default is at the end. wpnumber=wpnumber or #self.waypoints+1 @@ -693,13 +706,16 @@ function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) local speedkmh=UTILS.KnotsToKmph(speed) -- Create a Naval waypoint. - local wp=coordinate:WaypointNaval(speedkmh) + local wp=coordinate:WaypointGround(speedkmh, formation) - -- Add to table. - table.insert(self.waypoints, wpnumber, wp) + -- Create waypoint data table. + local waypoint=self:_CreateWaypoint(wp) + + -- Add waypoint to table. + self:_AddWaypoint(waypoint, wpnumber) -- Debug info. - self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) + self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) -- Update route. @@ -735,7 +751,7 @@ function ARMYGROUP:_InitGroup() -- Is (template) group late activated. self.isLateActivated=self.template.lateActivation - -- Naval groups cannot be uncontrolled. + -- Ground groups cannot be uncontrolled. self.isUncontrolled=false -- Max speed in km/h. @@ -766,6 +782,7 @@ function ARMYGROUP:_InitGroup() end end + -- Units of the group. local units=self.group:GetUnits() for _,_unit in pairs(units) do @@ -847,13 +864,13 @@ function ARMYGROUP:_CheckGroupDone(delay) local speed=self:GetSpeedToWaypoint(1) -- Start route at first waypoint. - self:__UpdateRoute(-1, 1, speed, self.depth) + self:__UpdateRoute(-1, 1, speed, self.formation) end else - self:UpdateRoute(nil, nil, self.depth) + self:UpdateRoute(nil, nil, self.formation) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index c08bf7a22..caef3f605 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2537,6 +2537,8 @@ function FLIGHTGROUP:_InitGroup() self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") end + -- Switch to default formation. + -- TODO: Should this be moved to onafterspawned? self:SwitchFormation(self.formationDefault) -- Add elemets. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index bd5a83106..58aebfd15 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -20,6 +20,7 @@ -- @field #NAVYGROUP.IntoWind intowind Into wind info. -- @field #table Qintowind Queue of "into wind" turns. -- @field #number depth Ordered depth in meters. +-- @field #boolean collisionwarning If true, collition warning. -- @extends Ops.OpsGroup#OPSGROUP --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson @@ -54,6 +55,7 @@ NAVYGROUP = { -- @field #number Speed Speed in knots. -- @field #number Offset Offset angle in degrees. -- @field #number Id Unique ID of the turn. +-- @field Ops.OpsGroup#OPSGROUP.Waypoint waypoint Turn into wind waypoint. -- @field Core.Point#COORDINATE Coordinate Coordinate where we left the route. -- @field #number Heading Heading the boat will take in degrees. -- @field #boolean Open Currently active. @@ -99,8 +101,8 @@ function NAVYGROUP:New(GroupName) self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position. - self:AddTransition("*", "TurnIntoWind", "*") -- Command the group to turn into the wind. - self:AddTransition("*", "TurnIntoWindOver", "*") -- Turn into wind is over. + self:AddTransition("*", "TurnIntoWind", "IntoWind") -- Command the group to turn into the wind. + self:AddTransition("*", "TurnIntoWindOver", "Cruising") -- Turn into wind is over. self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. @@ -108,6 +110,8 @@ function NAVYGROUP:New(GroupName) self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. + self:AddTransition("*", "CollitionWarning", "*") -- Collision warning. + self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive. self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface. @@ -170,7 +174,7 @@ function NAVYGROUP:SetPatrolAdInfinitum(switch) return self end ---- Group patrols ad inifintum. If the last waypoint is reached, it will go to waypoint one and repeat its route. +--- Set default cruise speed. This is the speed a group will take by default if no speed is specified explicitly. -- @param #NAVYGROUP self -- @param #number Speed Speed in knots. Default 70% of max speed. -- @return #NAVYGROUP self @@ -290,6 +294,36 @@ function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset) return recovery end + +--- Check if the group is currently holding its positon. +-- @param #NAVYGROUP self +-- @return #boolean If true, group was ordered to hold. +function NAVYGROUP:IsHolding() + return self:Is("Holding") +end + +--- Check if the group is currently cruising. +-- @param #NAVYGROUP self +-- @return #boolean If true, group cruising. +function NAVYGROUP:IsCruising() + return self:Is("Cruising") +end + +--- Check if the group is currently on a detour. +-- @param #NAVYGROUP self +-- @return #boolean If true, group is on a detour +function NAVYGROUP:IsOnDetour() + return self:Is("OnDetour") +end + + +--- Check if the group is currently diving. +-- @param #NAVYGROUP self +-- @return #boolean If true, group is currently diving. +function NAVYGROUP:IsDiving() + return self:Is("Diving") +end + --- Check if the group is currently turning. -- @param #NAVYGROUP self -- @return #boolean If true, group is currently turning. @@ -354,28 +388,41 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group started or stopped turning. self:_CheckTurning() - -- Check water is ahead. - local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist or 5000, hdg)) + local freepath=10000 + local collision=false - self:_CheckTurnsIntoWind() - - if self.intowind then + if not self:IsTurning() then - if timer.getAbsTime()>=self.intowind.Tstop then + if not self.ispathfinding then - self:TurnIntoWindOver() + freepath=self:_CheckFreePath(freepath, 100) + + if freepath<5000 then + self.ispathfinding=self:_FindPathToNextWaypoint() + end end - + + -- Check water is ahead. + --collision=self:_CheckCollisionCoord(pos:Translate(freepath+100, hdg)) + end + + -- Check into wind queue. + self:_CheckTurnsIntoWind() + + -- Check if group got stuck. + self:_CheckStuck() -- Get number of tasks and missions. local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() + + local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" -- Info text. - local text=string.format("State %s: Wp=%d/%d Speed=%.1f Heading=%03d intowind=%s turning=%s collision=%s Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, speed, hdg, tostring(self:IsSteamingIntoWind()), tostring(self:IsTurning()), tostring(collision), nTaskTot, nMissions) + local text=string.format("%s (T=%d,M=%d): Wp=%d-->%d (of %d) Speed=%.1f (%.1f) Depth=%.1f Hdg=%03d Turn=%s Collision=%d IntoWind=%s ROE=%d AS=%d", + fsmstate, nTaskTot, nMissions, self.currentwp, self:GetWaypointIndexNext(), #self.waypoints, speed, UTILS.MpsToKnots(self.speed or 0), pos.y, hdg, tostring(self:IsTurning()), freepath, intowind, 0, 0) self:I(self.lid..text) else @@ -444,7 +491,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) end - + -- Next status update in 10 seconds. self:__Status(-10) end @@ -510,8 +557,8 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number n Waypoint number. Default is next waypoint. --- @param #number Speed Speed in knots. Default cruise speed. --- @param #number Depth Depth in meters. Default 0 meters. +-- @param #number Speed Speed in knots to the next waypoint. +-- @param #number Depth Depth in meters to the next waypoint. function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Update route from this waypoint number onwards. @@ -526,52 +573,63 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Waypoints. local waypoints={} - -- Depth for submarines. - local depth=Depth or 0 - - -- Get current speed in km/h. - local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() - - -- Current waypoint. - local current=self:GetCoordinate():WaypointNaval(speed, depth) - table.insert(waypoints, current) - -- Add remaining waypoints to route. + local depth=nil for i=n, #self.waypoints do + + -- Waypoint. local wp=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint - -- Set speed. + -- Check if next wp. if i==n then + -- Speed. if Speed then + -- Take speed specified. wp.speed=UTILS.KnotsToMps(Speed) - elseif self.speedCruise then - wp.speed=UTILS.KmphToMps(self.speedCruise) - else - -- Take default waypoint speed. - end - - else - - if self.speedCruise then - wp.speed=UTILS.KmphToMps(self.speedCruise) else -- Take default waypoint speed. end + if Depth then + wp.alt=-Depth + elseif self.depth then + wp.alt=-self.depth + else + -- Take default waypoint alt. + end + + -- Current set speed in m/s. + self.speed=wp.speed + + -- Current set depth. + depth=wp.alt + + else + + -- Dive depth is applied to all other waypoints. + if self.depth then + wp.alt=-self.depth + else + -- Take default waypoint depth. + end + end - - -- Set depth. - wp.alt=-depth --Depth and -Depth or wp.alt - + -- Add waypoint. table.insert(waypoints, wp) end + + -- Current waypoint. + local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speed), depth) + table.insert(waypoints, 1, current) if #waypoints>1 then - - self:I(self.lid..string.format("Updateing route: WP=%d, Speed=%.1f knots, depth=%d meters", #self.waypoints-n+1, UTILS.KmphToKnots(speed), depth)) + + self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Depth=%d m", + self.currentwp, n, #self.waypoints, #waypoints-1, UTILS.MpsToKnots(self.speed), depth)) + -- Route group to all defined waypoints remaining. self:Route(waypoints) @@ -584,11 +642,6 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) self:I(self.lid..string.format("No waypoints left")) - if #self.waypoints>1 then - self:I(self.lid..string.format("Resuming route at first waypoint")) - self:__UpdateRoute(-1, 1, nil, self.depth) - end - end end @@ -601,7 +654,7 @@ end -- @param Core.Point#COORDINATE Coordinate Coordinate where to go. -- @param #number Speed Speed in knots. Default cruise speed. -- @param #number Depth Depth in meters. Default 0 meters. --- @param #number ResumeRoute If true, resume route after detour point was reached. +-- @param #number ResumeRoute If true, resume route after detour point was reached. If false, the group will stop at the detour point and wait for futher commands. function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute) -- Waypoints. @@ -610,20 +663,18 @@ function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, Resu -- Depth for submarines. local depth=Depth or 0 - -- Get current speed in km/h. - local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() + -- Speed in knots. + Speed=Speed or self:GetSpeedCruise() - -- Current waypoint. - local current=self:GetCoordinate():WaypointNaval(speed, depth) - table.insert(waypoints, current) + local wpindx=self.currentwp+1 - -- At each waypoint report passing. - local Task=self.group:TaskFunction("NAVYGROUP._DetourReached", self, ResumeRoute) + local wp=self:AddWaypoint(Coordinate, Speed, wpindx, true) - local detour=Coordinate:WaypointNaval(speed, depth, {Task}) - table.insert(waypoints, detour) - - self:Route(waypoints) + if ResumeRoute then + wp.detour=1 + else + wp.detour=0 + end end @@ -662,9 +713,6 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #NAVYGROUP.IntoWind Into wind parameters. --- @param #number Duration Duration in seconds. --- @param #number Speed Speed in knots. --- @param #boolean Uturn Return to the place we came from. function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) IntoWind.Heading=self:GetHeadingIntoWind(IntoWind.Offset) @@ -681,7 +729,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) -- Convert to knots. vwind=UTILS.MpsToKnots(vwind) - -- Speed of carrier in m/s but at least 2 knots. + -- Speed of carrier relative to wind but at least 2 knots. local speed=math.max(IntoWind.Speed-vwind, 2) -- Debug info. @@ -689,16 +737,28 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) local distance=UTILS.NMToMeters(1000) - local wp={} - local coord=self:GetCoordinate() local Coord=coord:Translate(distance, IntoWind.Heading) + local wpindex=self.currentwp+1 --self:GetWaypointIndexNext(false) + + local wptiw=self:AddWaypoint(Coord, speed, wpindex, true) + wptiw.intowind=true + + IntoWind.waypoint=wptiw + + if IntoWind.Uturn then + IntoWind.Coordinate:MarkToAll("Return coord") + end + + --[[ + local wp={} wp[1]=coord:WaypointNaval(UTILS.KnotsToKmph(speed)) wp[2]=Coord:WaypointNaval(UTILS.KnotsToKmph(speed)) self:Route(wp) - + ]] + end --- On after "TurnIntoWindOver" event. @@ -711,15 +771,22 @@ end -- @param #boolean Uturn Return to the place we came from. function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To) + env.info("FF Turn Into Wind Over!") + self.intowind.Over=true - self.intowind.Open=false + self.intowind.Open=false + + -- Remove additional waypoint. + self:RemoveWaypointByID(self.intowind.waypoint.uid) if self.intowind.Uturn then + env.info("FF Turn Into Wind Over Uturn!") self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true) else - local indx=self:GetWaypointIndexNext(self.adinfinitum) + env.info("FF Turn Into Wind Over Next WP!") + local indx=self:GetWaypointIndexNext() local speed=self:GetWaypointSpeed(indx) - self:UpdateRoute(indx, speed, self.depth) + self:__UpdateRoute(-1, indx, speed) end self.intowind=nil @@ -749,10 +816,13 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number Speed Speed in knots. +-- @param #number Speed Speed in knots until next waypoint is reached. function NAVYGROUP:onafterCruise(From, Event, To, Speed) - self:UpdateRoute(nil, Speed, self.depth) + -- + self.depth=nil + + self:__UpdateRoute(-1, nil, Speed) end @@ -762,7 +832,8 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param #number Depth Dive depth in meters. Default 50 meters. -function NAVYGROUP:onafterDive(From, Event, To, Depth) +-- @param #number Speed Speed in knots until next waypoint is reached. +function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed) Depth=Depth or 50 @@ -770,7 +841,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth) self.depth=Depth - self:UpdateRoute(nil, nil, self.depth) + self:__UpdateRoute(-1, nil, Speed) end @@ -779,11 +850,12 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function NAVYGROUP:onafterSurface(From, Event, To) +-- @param #number Speed Speed in knots until next waypoint is reached. +function NAVYGROUP:onafterSurface(From, Event, To, Speed) self.depth=0 - self:UpdateRoute(nil, nil, self.depth) + self:__UpdateRoute(-1, nil, Speed) end @@ -959,7 +1031,7 @@ function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) self:_AddWaypoint(waypoint, wpnumber) -- Debug info. - self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) + self:I(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d", wpnumber, waypoint.uid, speed, self.currentwp, #self.waypoints)) -- Update route. if updateroute==nil or updateroute==true then @@ -1089,6 +1161,73 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Check for possible collisions between two coordinates. +-- @param #NAVYGROUP self +-- @return #number Free distance in meters. +function NAVYGROUP:_CheckFreePath(DistanceMax, dx) + + local distance=DistanceMax or 5000 + local dx=dx or 100 + + -- If the group is turning, we cannot really tell anything about a possible collision. + if self:IsTurning() then + return distance + end + + -- Current coordinate. + local coordinate=self:GetCoordinate():SetAltitude(0, true) + + -- Current heading. + local heading=self:GetHeading() + + local function LoS(dist) + local checkcoord=coordinate:Translate(dist, heading, true) + return coordinate:IsLOS(checkcoord, 0.001) + end + + -- First check if everything is clear. + if LoS(DistanceMax) then + return DistanceMax + end + + local function check() + + local xmin=0 + local xmax=DistanceMax + + local Nmax=100 + local eps=100 + + local N=1 + while N<=Nmax do + + local d=xmax-xmin + local x=xmin+d/2 + + local los=LoS(x) + + env.info(string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) + + if los and d<=eps then + return x + end + + if los then + xmin=x + else + xmax=x + end + + N=N+1 + end + + return 0 + end + + + return check() +end + --- Check for possible collisions between two coordinates. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check. @@ -1197,6 +1336,32 @@ function NAVYGROUP:_CheckTurning() end +--- Check if group got stuck. +-- @param #NAVYGROUP self +function NAVYGROUP:_CheckStuck() + + if self:IsHolding() then + return + end + + local holdtime=0 + if self.holdtimestamp then + holdtime=timer.getTime()-self.holdtimestamp + end + + local ExpectedSpeed=self:GetExpectedSpeed() + + local speed=self.group:GetVelocityMPS() + + if speed<0.5 and ExpectedSpeed>0 then + if not self.holdtimestamp then + self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f expected=%.1f knots", speed, ExpectedSpeed)) + self.holdtimestamp=timer.getTime() + end + end + +end + --- Check if group is done, i.e. -- -- * passed the final waypoint, @@ -1284,6 +1449,14 @@ function NAVYGROUP:_CheckTurnsIntoWind() end end + + -- If into wind, check if over. + if self.intowind then + if timer.getAbsTime()>=self.intowind.Tstop then + self:TurnIntoWindOver() + end + end + end --- Get default cruise speed. @@ -1308,6 +1481,19 @@ function NAVYGROUP:GetSpeedToWaypoint(indx) return speed end +--- Returns the currently expected speed. +-- @param #NAVYGROUP self +-- @return #number Expected speed in m/s. +function NAVYGROUP:GetExpectedSpeed() + + if self:IsHolding() then + return 0 + else + return self.speed or 0 + end + +end + --- Check queued turns into wind. -- @param #NAVYGROUP self -- @return #NAVYGROUP.IntoWind Next into wind data. @@ -1363,6 +1549,85 @@ function NAVYGROUP:GetHeadingIntoWind(Offset) return intowind end +--- Find free path to next waypoint. +-- @param #NAVYGROUP self +-- @return #boolean If true, a path was found. +function NAVYGROUP:_FindPathToNextWaypoint() + + -- Pathfinding A* + local astar=ASTAR:New() + + -- Current positon of the group. + local position=self:GetCoordinate() + + -- Next waypoint. + local wpnext=self:GetWaypointNext() + + -- Next waypoint coordinate. + local nextwp=wpnext.coordinate + + -- If we are currently turning into the wind... + if wpnext.intowind then + local hdg=self:GetHeading() + nextwp=position:Translate(UTILS.NMToMeters(20), hdg, true) + end + + local speed=UTILS.MpsToKnots(wpnext.speed) + + -- Set start coordinate. + astar:SetStartCoordinate(position) + + -- Set end coordinate. + astar:SetEndCoordinate(nextwp) + + -- Distance to next waypoint. + local dist=position:Get2DDistance(nextwp) + + local boxwidth=dist*2 + local spacex=dist*0.1 + local delta=dist/10 + + -- Create a grid of nodes. We only want nodes of surface type water. + astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta*2, false) + + -- Valid neighbour nodes need to have line of sight. + astar:SetValidNeighbourLoS(400) + + --- Function to find a path and add waypoints to the group. + local function findpath() + + -- Calculate path from start to end node. + local path=astar:GetPath(true, true) + + if path then + + -- Loop over nodes in found path. + for i,_node in ipairs(path) do + local node=_node --Core.Astar#ASTAR.Node + + -- Waypoint index. + local wpindex=self:GetWaypointIndexCurrent()+i + + -- Add waypoints along detour path to next waypoint. + local wp=self:AddWaypoint(node.coordinate, speed, wpindex) + wp.astar=true + + -- Debug: smoke and mark path. + node.coordinate:MarkToAll(string.format("Path node #%d", i)) + + end + + return #path>0 + else + return false + end + + end + + return findpath() + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 237ab2316..ee8c0bc1c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1,5 +1,6 @@ --- **Ops** - Generic group enhancement functions. -- +-- This class is **not** meant to be used itself by the end user. -- -- === -- @@ -48,9 +49,10 @@ -- @field #boolean detectionOn If true, detected units of the group are analyzed. -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- --- @field Core.Point#COORDINATE position Current position of the group. +-- @field Core.Point#COORDINATE position Position of the group at last status check. -- @field #number traveldist Distance traveled in meters. This is a lower bound! -- @field #number traveltime Time. +-- @field #boolean ispathfinding If true, group is on pathfinding route. -- -- @field #number tacanChannelDefault The default TACAN channel. -- @field #string tacanMorseDefault The default TACAN morse code. @@ -70,16 +72,22 @@ -- @field #boolean eplrs If true, EPLRS data link is on. -- -- @field #string roeDefault Default ROE setting. --- @field #string rotDefault Default ROT setting. -- @field #string roe Current ROE setting. +-- +-- @field #string rotDefault Default ROT setting. -- @field #string rot Current ROT setting. -- +-- @field #string alarmstateDefault Default Alarm State setting. +-- @field #string alarmstate Current Alarm State setting. +-- -- @field #number formationDefault Default formation setting. -- @field #number formation Current formation setting. -- +-- @field Core.Astar#ASTAR Astar path finding. +-- -- @extends Core.Fsm#FSM ---- *Something must be left to chance; nothing is sure in a sea fight above all.* --- Horatio Nelson +--- *A small group of determined and like-minded people can change the course of history.* --- Mahatma Gandhi -- -- === -- @@ -87,7 +95,7 @@ -- -- # The OPSGROUP Concept -- --- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP and NAVYGROUP. +-- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP, NAVYGROUP. -- -- This class is **not** meant to be used itself by the end user. -- @@ -206,16 +214,18 @@ OPSGROUP.TaskType={ --- Waypoint data. -- @type OPSGROUP.Waypoint --- @field #table wp DCS waypoint table. --- @field Core.Point#COORDINATE coordinate Waypoint coordinate. --- @field #number speed Speed in m/s. --- @field #number altitude Altitude in meters. For submaries use negative sign for depth. --- @field #number index Waypoint index. This might change as waypoints are added and removed. -- @field #number uid Waypoint's unit id, which is a running number. --- @field #boolean onroad If true, ground group takes a road. --- @field #number formation The formation for this waypoint. --- @field #boolean detour If true, this waypoint is not part of the normal route. +-- @field #number speed Speed in m/s. +-- @field #number alt Altitude in meters. For submaries use negative sign for depth. -- @field #string action Waypoint action (turning point, etc.). Ground groups have the formation here. +-- @field #table task Waypoint task combo. +-- @field #string type Waypoint type. +-- @field #number x Waypoint x-coordinate. +-- @field #number y Waypoint y-coordinate. +-- @field #boolean detour If true, this waypoint is not part of the normal route. +-- @field #boolean intowind If true, this waypoint is a turn into wind route point. +-- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. +-- @field Core.Point#COORDINATE coordinate Waypoint coordinate. --- NavyGroup version. -- @field #string version @@ -433,79 +443,7 @@ function OPSGROUP:GetHeading() return nil end ---- Get next waypoint index. --- @param #OPSGROUP self --- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. Default is patrol ad infinitum value set. --- @return #number Next waypoint index. -function OPSGROUP:GetWaypointIndexNext(cyclic) - cyclic=cyclic or self.adinfinitum - - local N=#self.waypoints - - local n=math.min(self.currentwp+1, N) - - if cyclic and self.currentwp==N then - n=1 - end - - return n -end - ---- Get current waypoint index. This is the index of the last passed waypoint. --- @param #OPSGROUP self --- @return #number Current waypoint index. -function OPSGROUP:GetWaypointIndexCurrent() - return self.currentwp or 1 -end - ---- Get waypoint speed. --- @param #OPSGROUP self --- @param #number indx Waypoint index. --- @return #number Speed set at waypoint in knots. -function OPSGROUP:GetWaypointSpeed(indx) - - local waypoint=self:GetWaypoint(indx) - - if waypoint then - return UTILS.MpsToKnots(waypoint.speed) - end - - return nil -end - ---- Get waypoint. --- @param #OPSGROUP self --- @param #number indx Waypoint index. --- @return #OPSGROUP.Waypoint Waypoint table. -function OPSGROUP:GetWaypoint(indx) - return self.waypoints[indx] -end - ---- Get final waypoint. --- @param #OPSGROUP self --- @return #OPSGROUP.Waypoint Final waypoint table. -function OPSGROUP:GetWaypointFinal() - return self.waypoints[#self.waypoints] -end - ---- Get next waypoint. --- @param #OPSGROUP self --- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. --- @return #OPSGROUP.Waypoint Next waypoint table. -function OPSGROUP:GetWaypointNext(cyclic) - - local n=self:GetWaypointIndexNext(cyclic) - - return self.waypoints[n] -end - ---- Get current waypoint. --- @param #OPSGROUP self --- @return #OPSGROUP.Waypoint Current waypoint table. -function OPSGROUP:GetWaypointCurrent() - return self.waypoints[self.currentwp] -end --- Check if task description is unique. -- @param #OPSGROUP self @@ -524,29 +462,6 @@ function OPSGROUP:CheckTaskDescriptionUnique(description) return true end ---- Get coordinate of next waypoint of the group. --- @param #OPSGROUP self --- @return Core.Point#COORDINATE Coordinate of the next waypoint. --- @return #number Number of waypoint. -function OPSGROUP:GetNextWaypointCoordinate() - - -- Next waypoint. - local n=self:GetWaypointIndexNext(cyclic) - - -- Next waypoint. - local wp=self.waypoints[n] - - return self:GetWaypointCoordinate(wp) -end - ---- Get next waypoint coordinates. --- @param #OPSGROUP self --- @param #table wp Waypoint table. --- @return Core.Point#COORDINATE Coordinate of the next waypoint. -function OPSGROUP:GetWaypointCoordinate(wp) - -- TODO: move this to COORDINATE class. - return COORDINATE:New(wp.x, wp.alt, wp.y) -end --- Activate a *late activated* group. -- @param #OPSGROUP self @@ -707,6 +622,111 @@ function OPSGROUP:GetWaypointIndex(uid) return nil end +--- Get next waypoint index. +-- @param #OPSGROUP self +-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. Default is patrol ad infinitum value set. +-- @return #number Next waypoint index. +function OPSGROUP:GetWaypointIndexNext(cyclic) + + if cyclic==nil then + cyclic=self.adinfinitum + end + + --env.info("FF cyclic = "..tostring(cyclic)) + + local N=#self.waypoints + + local n=math.min(self.currentwp+1, N) + + --env.info("FF n = "..tostring(n)) + + if cyclic and self.currentwp==N then + n=1 + --env.info("FF cyclic n = "..tostring(n)) + end + + return n +end + +--- Get current waypoint index. This is the index of the last passed waypoint. +-- @param #OPSGROUP self +-- @return #number Current waypoint index. +function OPSGROUP:GetWaypointIndexCurrent() + return self.currentwp or 1 +end + +--- Get waypoint. +-- @param #OPSGROUP self +-- @param #number indx Waypoint index. +-- @return #OPSGROUP.Waypoint Waypoint table. +function OPSGROUP:GetWaypoint(indx) + return self.waypoints[indx] +end + +--- Get final waypoint. +-- @param #OPSGROUP self +-- @return #OPSGROUP.Waypoint Final waypoint table. +function OPSGROUP:GetWaypointFinal() + return self.waypoints[#self.waypoints] +end + +--- Get next waypoint. +-- @param #OPSGROUP self +-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. +-- @return #OPSGROUP.Waypoint Next waypoint table. +function OPSGROUP:GetWaypointNext(cyclic) + + local n=self:GetWaypointIndexNext(cyclic) + + return self.waypoints[n] +end + +--- Get current waypoint. +-- @param #OPSGROUP self +-- @return #OPSGROUP.Waypoint Current waypoint table. +function OPSGROUP:GetWaypointCurrent() + return self.waypoints[self.currentwp] +end + +--- Get coordinate of next waypoint of the group. +-- @param #OPSGROUP self +-- @return Core.Point#COORDINATE Coordinate of the next waypoint. +function OPSGROUP:GetNextWaypointCoordinate() + + -- Get next waypoint + local waypoint=self:GetWaypointNext(cyclic) + + return waypoint.coordinate +end + +--- Get waypoint coordinates. +-- @param #OPSGROUP self +-- @param #number index Waypoint index. +-- @return Core.Point#COORDINATE Coordinate of the next waypoint. +function OPSGROUP:GetWaypointCoordinate(index) + local waypoint=self:GetWaypoint(index) + if waypoint then + return waypoint.coordinate + end + return nil +end + +--- Get waypoint speed. +-- @param #OPSGROUP self +-- @param #number indx Waypoint index. +-- @return #number Speed set at waypoint in knots. +function OPSGROUP:GetWaypointSpeed(indx) + + local waypoint=self:GetWaypoint(indx) + + if waypoint then + return UTILS.MpsToKnots(waypoint.speed) + end + + return nil +end + + --- Remove a waypoint with a ceratin UID. -- @param #OPSGROUP self -- @param #number uid Waypoint UID. @@ -716,8 +736,7 @@ function OPSGROUP:RemoveWaypointByID(uid) local index=self:GetWaypointIndex(uid) if index then - self:RemoveWaypoint(index) - + self:RemoveWaypoint(index) end return self @@ -741,24 +760,47 @@ function OPSGROUP:RemoveWaypoint(wpindex) local n=#self.waypoints -- Debug info. - self:I(self.lid..string.format("Removing waypoint %d. N %d-->%d", wpindex, N, n)) + self:I(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) -- Waypoint was not reached yet. if wpindex > self.currentwp then - -- Could be that we just removed the only remaining waypoint ==> passedfinalwp=true so we RTB or wait. + -- Could be that we just removed the only remaining waypoint ==> passedfinalwp=true. + + -- TODO: patrol adinfinitum. + if self.currentwp>=n then self.passedfinalwp=true end - - self:_CheckGroupDone() + env.info("FF passed final waypoint after remove current wp = "..self.currentwp) + + self:_CheckGroupDone(1) + + --elseif wpindex==self.currentwp then + + -- Removed the waypoint we just passed. + else -- If an already passed waypoint was deleted, we do not need to update the route. - - -- TODO: But what about the self.currentwp number. This is now incorrect! - self.currentwp=self.currentwp-1 + + -- If current wp = 1 it stays 1. Otherwise decrease current wp. + + if self.currentwp==1 then + + if self.adinfinitum then + self.currentwp=#self.waypoints + else + self.currentwp=1 + end + + else + self.currentwp=self.currentwp-1 + end + + --self.currentwp=math.max(1, self.currentwp-1) + env.info("FF current waypoint after remove "..self.currentwp) end @@ -995,9 +1037,9 @@ end --- Get the unfinished waypoint tasks -- @param #OPSGROUP self --- @param #number n Waypoint index. Counting starts at one. +-- @param #number id Unique waypoint ID. -- @return #table Table of tasks. Table could also be empty {}. -function OPSGROUP:GetTasksWaypoint(n) +function OPSGROUP:GetTasksWaypoint(id) -- Tasks table. local tasks={} @@ -1008,7 +1050,7 @@ function OPSGROUP:GetTasksWaypoint(n) -- Look for first task that SCHEDULED. for _,_task in pairs(self.taskqueue) do local task=_task --#OPSGROUP.Task - if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==n then + if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED and task.waypoint==id then table.insert(tasks, task) end end @@ -1913,19 +1955,48 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n Waypoint passed. --- @param #number N Total number of waypoints. -- @param #OPSGROUP.Waypoint Waypoint Waypoint data passed. -function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, Waypoint) - local text=string.format("Group passed waypoint %d/%d", n, N) - self:T(self.lid..text) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) +function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) + -- Apply tasks of this waypoint. + local ntasks=self:_SetWaypointTasks(Waypoint) + + -- Get waypoint index. + local wpindex=self:GetWaypointIndex(Waypoint.uid) + + -- Final waypoint reached? + if wpindex==nil or wpindex==#self.waypoints then + + -- Set switch to true. + self.passedfinalwp=true + + -- Check if all tasks/mission are done? If so, RTB or WAIT. + -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. + if ntasks==0 then + self:_CheckGroupDone(1) + end + + end + + -- Debug info. + local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", + tostring(wpindex), #self.waypoints, Waypoint.uid, tostring(self.passedfinalwp), tostring(Waypoint.detour), tostring(Waypoint.astar)) + self:I(self.lid..text) + MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) + +end + +--- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there. +-- @param #OPSGROUP self +-- @param #OPSGROUP.Waypoint Waypoint The waypoint. +-- @return #number Number of tasks. +function OPSGROUP:_SetWaypointTasks(Waypoint) + -- Get all waypoint tasks. local tasks=self:GetTasksWaypoint(Waypoint.uid) - + -- Debug info. - local text=string.format("WP %d/%d tasks:", n, N) + local text=string.format("WP uid=%d tasks:", Waypoint.uid) if #tasks>0 then for i,_task in pairs(tasks) do local task=_task --#OPSGROUP.Task @@ -1963,20 +2034,8 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, n, N, Waypoint) if #taskswp>0 then self:SetTask(self.group:TaskCombo(taskswp)) end - - -- Final AIR waypoint reached? - if n==N then - -- Set switch to true. - self.passedfinalwp=true - - -- Check if all tasks/mission are done? If so, RTB or WAIT. - -- Note, we delay it for a second to let the OnAfterPassingwaypoint function to be executed in case someone wants to add another waypoint there. - if #taskswp==0 then - self:_CheckGroupDone(1) - end - - end + return #taskswp end --- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there. @@ -2195,14 +2254,17 @@ end --- Initialize Mission Editor waypoints. -- @param #OPSGROUP self --- @param #table waypoint DCS waypoint data table. +-- @param #OPSGROUP.Waypoint waypoint DCS waypoint data table. -- @return #OPSGROUP.Waypoint Waypoint data. -function OPSGROUP:_CreateWaypoint(waypoint, detour, onroad, formation) +function OPSGROUP:_CreateWaypoint(waypoint, formation, detour) waypoint.uid=self.wpcounter waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) waypoint.detour=detour and detour or false waypoint.formation=formation + if formation then + waypoint.action=formation + end waypoint.onroad=onroad and onroad or false self.wpcounter=self.wpcounter+1 @@ -2218,7 +2280,7 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) wpnumber=wpnumber or #self.waypoints+1 - env.info(string.format("adding waypoint at index=%d", wpnumber)) + self:I(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) -- Add waypoint to table. table.insert(self.waypoints, wpnumber, waypoint) @@ -2263,7 +2325,7 @@ end --- Route group along waypoints. -- @param #OPSGROUP self -- @param #table waypoints Table of waypoints. --- @default +-- @param #number delay Delay in seconds. -- @return #OPSGROUP self function OPSGROUP:Route(waypoints, delay) @@ -2314,13 +2376,13 @@ function OPSGROUP:_UpdateWaypointTasks(n) if i>=n or nwaypoints==1 then -- Debug info. - self:T(self.lid..string.format("Updating waypoint task for waypoint %d/%d. Last waypoint passed %d", i, nwaypoints, self.currentwp)) + self:I(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d", i, nwaypoints, wp.uid, self.currentwp)) -- Tasks of this waypoint local taskswp={} -- At each waypoint report passing. - local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, i, wp.uid) + local TaskPassingWaypoint=self.group:TaskFunction("OPSGROUP._PassingWaypoint", self, wp.uid) table.insert(taskswp, TaskPassingWaypoint) -- Waypoint task combo. @@ -2337,26 +2399,72 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Function called when a group is passing a waypoint. ---@param Wrapper.Group#GROUP group Group that passed the waypoint +--@param Wrapper.Group#GROUP group Group that passed the waypoint. --@param #OPSGROUP opsgroup Ops group object. ---@param #number i Waypoint number that has been reached. --@param #number uid Waypoint UID. -function OPSGROUP._PassingWaypoint(group, opsgroup, i, uid) - - local final=#opsgroup.waypoints or 1 - - -- Debug message. - local text=string.format("Group passing waypoint %d of %d, uid=%d", i, final, uid) - opsgroup:I(opsgroup.lid..text) - - -- Set current waypoint. - opsgroup.currentwp=i +function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Get waypoint data. local waypoint=opsgroup:GetWaypointByID(uid) - - -- Trigger PassingWaypoint event. - opsgroup:PassingWaypoint(i, final, waypoint) + + if waypoint then + + -- Get the current waypoint index. + opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) + + -- Set expected speed and formation from the next WP. + local wpnext=opsgroup:GetWaypointNext() + if wpnext then + + -- Set formation. + if opsgroup.isGround then + opsgroup.formation=wpnext.action + end + + -- Set speed. + opsgroup.speed=wpnext.speed + + end + + -- Check if the group is still pathfinding. + if opsgroup.ispathfinding and not waypoint.astar then + opsgroup.ispathfinding=false + end + + -- Check special waypoints. + if waypoint.astar then + + env.info("FF removing Astar waypoint "..uid) + opsgroup:RemoveWaypointByID(uid) + + elseif waypoint.detour then + + env.info("FF removing Detour waypoint "..uid) + opsgroup:RemoveWaypointByID(uid) + + -- Trigger event. + opsgroup:DetourReached() + + if waypoint.detour==0 then + opsgroup:FullStop() + elseif waypoint.detour==1 then + opsgroup:Cruise() + else + opsgroup:E("ERROR: waypoint.detour should be 0 or 1") + end + + end + + -- Debug message. + local text=string.format("Group passing waypoint uid=%d", uid) + opsgroup:I(opsgroup.lid..text) + + -- Trigger PassingWaypoint event. + if not (waypoint.astar or waypoint.detour) then + opsgroup:PassingWaypoint(waypoint) + end + + end end @@ -2461,6 +2569,52 @@ function OPSGROUP:SetOptionROT(rot) return self end + +--- Set the default Alarm State for the group. This is the state gets when the group is spawned or to which it defaults back after a mission. +-- @param #OPSGROUP self +-- @param #number alarmstate Alarm state of group. Default is `AI.Option.Ground.val.ALARM_STATE.AUTO` (0). +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultAlarmstate(alarmstate) + self.alarmstateDefault=alarmstate or 0 + return self +end + +--- Set current Alarm State of the group. +-- @param #OPSGROUP self +-- @param #string alarmstate Alarm state of group. Default is the value defined by :SetDefaultAlarmstate(). +-- @return #OPSGROUP self +function OPSGROUP:SetOptionAlarmstate(alarmstate) + + self.alarmstate=alarmstate or self.alarmstateDefault + + if self:IsAlive() then + + if self.alarmstate==0 then + self.group:OptionAlarmStateAuto() + elseif self.alarmstate==1 then + self.group:OptionAlarmStateGreen() + elseif self.alarmstate==2 then + self.group:OptionAlarmStateRed() + else + self:E("ERROR: Unknown Alarm State! Setting to AUTO.") + self.group:OptionAlarmStateAuto() + end + + self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.alarmstate)) + else + -- TODO WARNING + end + + return self +end + +--- Get current Alarm State of the group. +-- @param #OPSGROUP self +-- @return #number Current Alarm State. +function OPSGROUP:GetAlarmstate() + return self.alarmstate +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Element and Group Status Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index eaef38b47..947b23299 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -222,6 +222,15 @@ ENUMS.Formation.RotaryWing.EchelonLeft={} ENUMS.Formation.RotaryWing.EchelonLeft.D70 =590081 ENUMS.Formation.RotaryWing.EchelonLeft.D300=590082 ENUMS.Formation.RotaryWing.EchelonLeft.D600=590083 +ENUMS.Formation.Vehicle={} +ENUMS.Formation.Vehicle.Vee="Vee" +ENUMS.Formation.Vehicle.EchelonRight="EchelonR" +ENUMS.Formation.Vehicle.OffRoad="Off Road" +ENUMS.Formation.Vehicle.Rank="Rank" +ENUMS.Formation.Vehicle.EchelonLeft="EchelonL" +ENUMS.Formation.Vehicle.OnRoad="On Road" +ENUMS.Formation.Vehicle.Cone="Cone" +ENUMS.Formation.Vehicle.Diamond="Diamond" --- Formations (old). The old format is a simplified version of the new formation enums, which allow more sophisticated settings. -- See the [Formations](https://wiki.hoggitworld.com/view/DCS_enum_formation) on hoggit wiki. From 0d42b12658f3606b7ec7461e346636df5ba1b48a Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 30 Jul 2020 01:08:25 +0200 Subject: [PATCH 18/79] Enhanced Groups --- Moose Development/Moose/Ops/ArmyGroup.lua | 148 +++-------- Moose Development/Moose/Ops/FlightGroup.lua | 1 - Moose Development/Moose/Ops/NavyGroup.lua | 205 +++++---------- Moose Development/Moose/Ops/OpsGroup.lua | 272 +++++++++++++++++--- 4 files changed, 352 insertions(+), 274 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 6e9cba673..3d8a66ae1 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -458,27 +458,25 @@ end -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate Coordinate where to go. -- @param #number Speed Speed in knots. Default cruise speed. --- @param #number Formation Formation the group will use. --- @param #number ResumeRoute If true, resume route after detour point was reached. +-- @param #number Formation Formation of the group. +-- @param #number ResumeRoute If true, resume route after detour point was reached. If false, the group will stop at the detour point and wait for futher commands. function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, ResumeRoute) - -- Waypoints. - local waypoints={} - - -- Get current speed in km/h. - local speed=Speed and UTILS.KnotsToKmph(Speed) or self.group:GetVelocityKMH() + -- Speed in knots. + Speed=Speed or self:GetSpeedCruise() - -- Current waypoint. - local current=self:GetCoordinate():WaypointGround(Speed, Formation) - table.insert(waypoints, current) + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid - -- At each waypoint report passing. - local Task=self.group:TaskFunction("ARMYGROUP._DetourReached", self, ResumeRoute) + -- Add waypoint after current. + local wp=self:AddWaypoint(Coordinate, Speed, uid, Formation, true) - local detour=Coordinate:WaypointGround(Speed, Formation, {Task}) - table.insert(waypoints, detour) - - self:Route(waypoints) + -- Set if we want to resume route after reaching the detour waypoint. + if ResumeRoute then + wp.detour=1 + else + wp.detour=0 + end end @@ -491,25 +489,6 @@ function ARMYGROUP:onafterDetourReached(From, Event, To) self:I(self.lid.."Group reached detour coordinate.") end ---- Function called when a group is passing a waypoint. ---@param Wrapper.Group#GROUP group Group that passed the waypoint ---@param #ARMYGROUP armygroup Army group object. ---@param #boolean resume Resume route. -function ARMYGROUP._DetourReached(group, armygroup, resume) - - -- Debug message. - local text=string.format("Group reached detour coordinate") - armygroup:I(armygroup.lid..text) - - if resume then - local indx=armygroup:GetWaypointIndexNext(true) - local speed=armygroup:GetSpeedToWaypoint(indx) - armygroup:__UpdateRoute(-1, indx, speed, armygroup.formation) - end - - armygroup:DetourReached() - -end --- On after "FullStop" event. -- @param #ARMYGROUP self @@ -684,29 +663,36 @@ end --- Add an a waypoint to the route. -- @param #ARMYGROUP self --- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. --- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed. --- @param #number wpnumber Waypoint number. Default at the end. --- @param #number formation Formation the group will use. --- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. --- @return #number Waypoint index. -function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, formation, updateroute) +-- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. +-- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. +-- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. +-- @param #number Formation Formation the group will use. +-- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. +-- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. +function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) - -- Waypoint number. Default is at the end. - wpnumber=wpnumber or #self.waypoints+1 - + -- Set waypoint index. + local wpnumber=#self.waypoints+1 + if wpnumber then + local index=self:GetWaypointIndex(AfterWaypointWithID) + if index then + wpnumber=index+1 + end + end + + -- Check if final waypoint is still passed. if wpnumber>self.currentwp then self.passedfinalwp=false end -- Speed in knots. - speed=speed or self:GetSpeedCruise() + Speed=Speed or self:GetSpeedCruise() -- Speed at waypoint. - local speedkmh=UTILS.KnotsToKmph(speed) + local speedkmh=UTILS.KnotsToKmph(Speed) -- Create a Naval waypoint. - local wp=coordinate:WaypointGround(speedkmh, formation) + local wp=Coordinate:WaypointGround(speedkmh, Formation) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) @@ -715,15 +701,15 @@ function ARMYGROUP:AddWaypoint(coordinate, speed, wpnumber, formation, updaterou self:_AddWaypoint(waypoint, wpnumber) -- Debug info. - self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) + self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) -- Update route. - if updateroute==nil or updateroute==true then + if Updateroute==nil or Updateroute==true then self:_CheckGroupDone(1) end - return wpnumber + return waypoint end --- Initialize group parameters. Also initializes waypoints if self.waypoints is nil. @@ -822,7 +808,7 @@ function ARMYGROUP:_InitGroup() --text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) --text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) self:I(self.lid..text) @@ -838,49 +824,6 @@ end -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Check if group is done, i.e. --- --- * passed the final waypoint, --- * no current task --- * no current mission --- * number of remaining tasks is zero --- * number of remaining missions is zero --- --- @param #ARMYGROUP self --- @param #number delay Delay in seconds. -function ARMYGROUP:_CheckGroupDone(delay) - - if self:IsAlive() and self.ai then - - if delay and delay>0 then - -- Delayed call. - self:ScheduleOnce(delay, ARMYGROUP._CheckGroupDone, self) - else - - if self.passedfinalwp then - - if #self.waypoints>1 and self.adinfinitum then - - local speed=self:GetSpeedToWaypoint(1) - - -- Start route at first waypoint. - self:__UpdateRoute(-1, 1, speed, self.formation) - - end - - else - - self:UpdateRoute(nil, nil, self.formation) - - end - - end - - end - -end - - --- Get default cruise speed. -- @param #ARMYGROUP self -- @return #number Cruise speed (>0) in knots. @@ -888,21 +831,6 @@ function ARMYGROUP:GetSpeedCruise() return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) end ---- Returns a non-zero speed to the next waypoint (even if the waypoint speed is zero). --- @param #ARMYGROUP self --- @param #number indx Waypoint index. --- @return #number Speed to next waypoint (>0) in knots. -function ARMYGROUP:GetSpeedToWaypoint(indx) - - local speed=self:GetWaypointSpeed(indx) - - if speed<=0.1 then - speed=self:GetSpeedCruise() - end - - return speed -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index caef3f605..940da0b03 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1778,7 +1778,6 @@ function FLIGHTGROUP:_CheckGroupDone(delay) return end - -- Number of tasks remaining. local nTasks=self:CountRemainingTasks() diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 58aebfd15..fbd4fa740 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -93,6 +93,7 @@ function NAVYGROUP:New(GroupName) -- Defaults self:SetDefaultROE() + self:SetDefaultAlarmstate() self:SetDetection() self:SetPatrolAdInfinitum(true) @@ -418,11 +419,23 @@ function NAVYGROUP:onafterStatus(From, Event, To) local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() - local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" + local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" + local turning=tostring(self:IsTurning()) + local alt=pos.y + local speedExpected=UTILS.MpsToKnots(self.speed or 0) + + local wpidxCurr=self.currentwp + local wpuidCurr=0 + local wpidxNext=self:GetWaypointIndexNext() + local wpuidNext=0 + local wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint()) + local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint(), true) + local roe=self:GetROE() or 0 + local als=self:GetAlarmstate() or 0 -- Info text. - local text=string.format("%s (T=%d,M=%d): Wp=%d-->%d (of %d) Speed=%.1f (%.1f) Depth=%.1f Hdg=%03d Turn=%s Collision=%d IntoWind=%s ROE=%d AS=%d", - fsmstate, nTaskTot, nMissions, self.currentwp, self:GetWaypointIndexNext(), #self.waypoints, speed, UTILS.MpsToKnots(self.speed or 0), pos.y, hdg, tostring(self:IsTurning()), freepath, intowind, 0, 0) + local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", + fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints, wpDist, wpETA, speed, speedExpected, alt, hdg, turning, freepath, intowind) self:I(self.lid..text) else @@ -536,16 +549,15 @@ function NAVYGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE and ROT options. - self:SetOptionROE(self.roe) + -- Set default ROE and Alarmstate options. + self:SetOptionROE(self.roe) + self:SetOptionAlarmstate(self.alarmstate) end -- Get orientation. self.Corientlast=self.group:GetUnit(1):GetOrientationX() - self.depth=self.group:GetHeight() - -- Update route. self:Cruise() @@ -562,7 +574,7 @@ end function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) -- Update route from this waypoint number onwards. - n=n or self:GetWaypointIndexNext(self.adinfinitum) + n=n or self:GetWaypointIndexNext() -- Debug info. self:T(self.lid..string.format("FF Update route n=%d", n)) @@ -607,7 +619,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) else - -- Dive depth is applied to all other waypoints. + -- Dive depth is applied to all other waypoints. if self.depth then wp.alt=-self.depth else @@ -637,10 +649,11 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) else --- - -- No waypoints left + -- No waypoints left ==> Full Stop --- - self:I(self.lid..string.format("No waypoints left")) + self:E(self.lid..string.format("WARNING: No waypoints left ==> Full Stop!")) + self:FullStop() end @@ -656,20 +669,20 @@ end -- @param #number Depth Depth in meters. Default 0 meters. -- @param #number ResumeRoute If true, resume route after detour point was reached. If false, the group will stop at the detour point and wait for futher commands. function NAVYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Depth, ResumeRoute) - - -- Waypoints. - local waypoints={} -- Depth for submarines. - local depth=Depth or 0 + Depth=Depth or 0 -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - local wpindx=self.currentwp+1 + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid - local wp=self:AddWaypoint(Coordinate, Speed, wpindx, true) + -- Add waypoint after current. + local wp=self:AddWaypoint(Coordinate, Speed, uid, Depth, true) + -- Set if we want to resume route after reaching the detour waypoint. if ResumeRoute then wp.detour=1 else @@ -687,26 +700,6 @@ function NAVYGROUP:onafterDetourReached(From, Event, To) self:I(self.lid.."Group reached detour coordinate.") end ---- Function called when a group is passing a waypoint. ---@param Wrapper.Group#GROUP group Group that passed the waypoint ---@param #NAVYGROUP navygroup Navy group object. ---@param #boolean resume Resume route. -function NAVYGROUP._DetourReached(group, navygroup, resume) - - -- Debug message. - local text=string.format("Group reached detour coordinate") - navygroup:I(navygroup.lid..text) - - if resume then - local indx=navygroup:GetWaypointIndexNext(true) - local speed=navygroup:GetSpeedToWaypoint(indx) - navygroup:UpdateRoute(indx, speed, navygroup.depth) - end - - navygroup:DetourReached() - -end - --- On after "TurnIntoWind" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -739,10 +732,11 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) local coord=self:GetCoordinate() local Coord=coord:Translate(distance, IntoWind.Heading) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid - local wpindex=self.currentwp+1 --self:GetWaypointIndexNext(false) - - local wptiw=self:AddWaypoint(Coord, speed, wpindex, true) + local wptiw=self:AddWaypoint(Coord, speed, uid) wptiw.intowind=true IntoWind.waypoint=wptiw @@ -751,14 +745,6 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) IntoWind.Coordinate:MarkToAll("Return coord") end - --[[ - local wp={} - wp[1]=coord:WaypointNaval(UTILS.KnotsToKmph(speed)) - wp[2]=Coord:WaypointNaval(UTILS.KnotsToKmph(speed)) - - self:Route(wp) - ]] - end --- On after "TurnIntoWindOver" event. @@ -816,10 +802,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number Speed Speed in knots until next waypoint is reached. +-- @param #number Speed Speed in knots until next waypoint is reached. Default is speed set for waypoint. function NAVYGROUP:onafterCruise(From, Event, To, Speed) - -- + -- No set depth. self.depth=nil self:__UpdateRoute(-1, nil, Speed) @@ -1001,28 +987,36 @@ end --- Add an a waypoint to the route. -- @param #NAVYGROUP self --- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. --- @param #number speed Speed in knots. Default is default cruise speed or 70% of max speed. --- @param #number wpnumber Waypoint number. Default at the end. --- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. +-- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. +-- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. +-- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. +-- @param #number Depth Depth at waypoint in meters. +-- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. -function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) - - -- Waypoint number. Default is at the end. - wpnumber=wpnumber or #self.waypoints+1 +function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Updateroute) + -- Set waypoint index. + local wpnumber=#self.waypoints+1 + if wpnumber then + local index=self:GetWaypointIndex(AfterWaypointWithID) + if index then + wpnumber=index+1 + end + end + + -- Check if final waypoint is still passed. if wpnumber>self.currentwp then self.passedfinalwp=false end -- Speed in knots. - speed=speed or self:GetSpeedCruise() + Speed=Speed or self:GetSpeedCruise() -- Speed at waypoint. - local speedkmh=UTILS.KnotsToKmph(speed) + local speedkmh=UTILS.KnotsToKmph(Speed) -- Create a Naval waypoint. - local wp=coordinate:WaypointNaval(speedkmh) + local wp=Coordinate:WaypointNaval(speedkmh) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) @@ -1031,10 +1025,10 @@ function NAVYGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) self:_AddWaypoint(waypoint, wpnumber) -- Debug info. - self:I(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d", wpnumber, waypoint.uid, speed, self.currentwp, #self.waypoints)) + self:I(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d", wpnumber, waypoint.uid, Speed, self.currentwp, #self.waypoints)) -- Update route. - if updateroute==nil or updateroute==true then + if Updateroute==nil or Updateroute==true then self:_CheckGroupDone(1) end @@ -1076,8 +1070,8 @@ function NAVYGROUP:_InitGroup() -- Max speed in km/h. self.speedmax=self.group:GetSpeedMax() - -- Cruise speed: 70% of max speed but within limit. - --self.speedCruise=self.speedmax*0.7 + -- Cruise speed: 70% of max speed. + self.speedCruise=self.speedmax*0.7 -- Group ammo. --self.ammo=self:GetAmmoTot() @@ -1106,6 +1100,7 @@ function NAVYGROUP:_InitGroup() end end + -- Get all units of the group. local units=self.group:GetUnits() for _,_unit in pairs(units) do @@ -1351,58 +1346,18 @@ function NAVYGROUP:_CheckStuck() local ExpectedSpeed=self:GetExpectedSpeed() - local speed=self.group:GetVelocityMPS() + local speed=self:GetVelocity() if speed<0.5 and ExpectedSpeed>0 then if not self.holdtimestamp then - self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f expected=%.1f knots", speed, ExpectedSpeed)) + self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected", speed, ExpectedSpeed)) self.holdtimestamp=timer.getTime() end end end ---- Check if group is done, i.e. --- --- * passed the final waypoint, --- * no current task --- * no current mission --- * number of remaining tasks is zero --- * number of remaining missions is zero --- --- @param #NAVYGROUP self --- @param #number delay Delay in seconds. -function NAVYGROUP:_CheckGroupDone(delay) - if self:IsAlive() and self.ai then - - if delay and delay>0 then - -- Delayed call. - self:ScheduleOnce(delay, NAVYGROUP._CheckGroupDone, self) - else - - if self.passedfinalwp then - - if #self.waypoints>1 and self.adinfinitum then - - local speed=self:GetSpeedToWaypoint(1) - - -- Start route at first waypoint. - self:__UpdateRoute(-1, 1, speed, self.depth) - - end - - else - - self:__UpdateRoute(-1, nil, nil, self.depth) - - end - - end - - end - -end --- Check queued turns into wind. -- @param #NAVYGROUP self @@ -1466,33 +1421,7 @@ function NAVYGROUP:GetSpeedCruise() return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) end ---- Returns a non-zero speed to the next waypoint (even if the waypoint speed is zero). --- @param #NAVYGROUP self --- @param #number indx Waypoint index. --- @return #number Speed to next waypoint (>0) in knots. -function NAVYGROUP:GetSpeedToWaypoint(indx) - local speed=self:GetWaypointSpeed(indx) - - if speed<=0.1 then - speed=self:GetSpeedCruise() - end - - return speed -end - ---- Returns the currently expected speed. --- @param #NAVYGROUP self --- @return #number Expected speed in m/s. -function NAVYGROUP:GetExpectedSpeed() - - if self:IsHolding() then - return 0 - else - return self.speed or 0 - end - -end --- Check queued turns into wind. -- @param #NAVYGROUP self @@ -1602,15 +1531,23 @@ function NAVYGROUP:_FindPathToNextWaypoint() if path then -- Loop over nodes in found path. + local uid=self:GetWaypointCurrent().uid -- ID of current waypoint. + for i,_node in ipairs(path) do local node=_node --Core.Astar#ASTAR.Node -- Waypoint index. local wpindex=self:GetWaypointIndexCurrent()+i + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid -- Add waypoints along detour path to next waypoint. - local wp=self:AddWaypoint(node.coordinate, speed, wpindex) + local wp=self:AddWaypoint(node.coordinate, speed, uid) wp.astar=true + + -- Update id so the next wp is added after this one. + uid=wp.uid -- Debug: smoke and mark path. node.coordinate:MarkToAll(string.format("Path node #%d", i)) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ee8c0bc1c..9587aa307 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -95,9 +95,10 @@ -- -- # The OPSGROUP Concept -- --- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP, NAVYGROUP. +-- The OPSGROUP class contains common functions used by other classes such as FLIGHGROUP, NAVYGROUP and ARMYGROUP. +-- Those classes inherit everything of this class and extend it with features specific to their unit category. -- --- This class is **not** meant to be used itself by the end user. +-- This class is **NOT** meant to be used by the end user itself. -- -- -- @field #OPSGROUP @@ -229,7 +230,7 @@ OPSGROUP.TaskType={ --- NavyGroup version. -- @field #string version -OPSGROUP.version="0.1.0" +OPSGROUP.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -431,6 +432,18 @@ function OPSGROUP:GetCoordinate() return nil end +--- Get current velocity of the group. +-- @param #OPSGROUP self +-- @return #number Velocity in m/s. +function OPSGROUP:GetVelocity() + if self:IsAlive()~=nil then + return self.group:GetVelocityMPS() + else + self:E(self.lid.."WARNING: Group is not alive. Cannot get velocity!") + end + return nil +end + --- Get current heading of the group. -- @param #OPSGROUP self -- @return #number Current heading of the group in degrees. @@ -690,8 +703,9 @@ end --- Get coordinate of next waypoint of the group. -- @param #OPSGROUP self +-- @param #boolean cyclic If true, return first waypoint if last waypoint was reached. -- @return Core.Point#COORDINATE Coordinate of the next waypoint. -function OPSGROUP:GetNextWaypointCoordinate() +function OPSGROUP:GetNextWaypointCoordinate(cyclic) -- Get next waypoint local waypoint=self:GetWaypointNext(cyclic) @@ -726,6 +740,98 @@ function OPSGROUP:GetWaypointSpeed(indx) return nil end +--- Get unique ID of waypoint. +-- @param #OPSGROUP self +-- @param #number indx Waypoint index. +-- @return #number Unique ID. +function OPSGROUP:GetWaypointID(indx) + + local waypoint=self:GetWaypoint(indx) + + if waypoint then + return waypoint.uid + end + + return nil + +end + +--- Returns a non-zero speed to the next waypoint (even if the waypoint speed is zero). +-- @param #OPSGROUP self +-- @param #number indx Waypoint index. +-- @return #number Speed to next waypoint (>0) in knots. +function OPSGROUP:GetSpeedToWaypoint(indx) + + local speed=self:GetWaypointSpeed(indx) + + if speed<=0.1 then + speed=self:GetSpeedCruise() + end + + return speed +end + +--- Get distance to waypoint. +-- @param #OPSGROUP self +-- @param #number indx Waypoint index. Default is the next waypoint. +-- @return #number Distance in meters. +function OPSGROUP:GetDistanceToWaypoint(indx) + local dist=0 + + if #self.waypoints>0 then + + indx=indx or self:GetWaypointIndexNext() + + local wp=self:GetWaypoint(indx) + + if wp then + + local coord=self:GetCoordinate() + + dist=coord:Get2DDistance(wp.coordinate) + end + + end + + return dist +end + +--- Get time to waypoint based on current velocity. +-- @param #OPSGROUP self +-- @param #number indx Waypoint index. Default is the next waypoint. +-- @return #number Time in seconds. If velocity is 0 +function OPSGROUP:GetTimeToWaypoint(indx) + + local s=self:GetDistanceToWaypoint(indx) + + local v=self:GetVelocity() + + local t=s/v + + if t==math.inf then + return 365*24*60*60 + elseif t==math.nan then + return 0 + else + return t + end + +end + +--- Returns the currently expected speed. +-- @param #OPSGROUP self +-- @return #number Expected speed in m/s. +function OPSGROUP:GetExpectedSpeed() + + if self:IsHolding() then + return 0 + else + return self.speed or 0 + end + +end + + --- Remove a waypoint with a ceratin UID. -- @param #OPSGROUP self @@ -765,7 +871,9 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- Waypoint was not reached yet. if wpindex > self.currentwp then - -- Could be that we just removed the only remaining waypoint ==> passedfinalwp=true. + --- + -- Removed a FUTURE waypoint + --- -- TODO: patrol adinfinitum. @@ -773,16 +881,16 @@ function OPSGROUP:RemoveWaypoint(wpindex) self.passedfinalwp=true end - env.info("FF passed final waypoint after remove current wp = "..self.currentwp) + env.info("FF passed final waypoint after remove! current wp = "..self.currentwp) self:_CheckGroupDone(1) - - --elseif wpindex==self.currentwp then - - -- Removed the waypoint we just passed. - + else + --- + -- Removed a waypoint ALREADY PASSED + --- + -- If an already passed waypoint was deleted, we do not need to update the route. -- If current wp = 1 it stays 1. Otherwise decrease current wp. @@ -962,20 +1070,17 @@ end --- Add a *waypoint* task. -- @param #OPSGROUP self -- @param #table task DCS task table structure. --- @param #number waypointindex Number of waypoint. Counting starts at one! Default is the as *next* waypoint. +-- @param #OPSGROUP.Waypoint Waypoint where the task is executed. Default is the at *next* waypoint. -- @param #string description Brief text describing the task, e.g. "Attack SAM". -- @param #number prio Priority of the task. Number between 1 and 100. Default is 50. -- @param #number duration Duration before task is cancelled in seconds counted after task started. Default never. -- @return #OPSGROUP.Task The task structure. -function OPSGROUP:AddTaskWaypoint(task, waypointindex, description, prio, duration) - - -- Index. - waypointindex=waypointindex or (self.currentwp and self.currentwp+1 or 2) +function OPSGROUP:AddTaskWaypoint(task, Waypoint, description, prio, duration) - -- Get waypoint - local waypoint=self:GetWaypointByIndex(waypointindex) - - if waypoint then + -- Waypoint of task. + Waypoint=Waypoint or self:GetWaypointNext() + + if Waypoint then -- Increase counter. self.taskcounter=self.taskcounter+1 @@ -989,7 +1094,7 @@ function OPSGROUP:AddTaskWaypoint(task, waypointindex, description, prio, durati newtask.id=self.taskcounter newtask.duration=duration newtask.time=0 - newtask.waypoint=waypoint.uid + newtask.waypoint=Waypoint.uid newtask.type=OPSGROUP.TaskType.WAYPOINT newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id)) newtask.stopflag:Set(0) @@ -1002,10 +1107,9 @@ function OPSGROUP:AddTaskWaypoint(task, waypointindex, description, prio, durati self:T3({newtask=newtask}) -- Update route. - --self:_CheckGroupDone(1) self:__UpdateRoute(-1) - return newtask + return newtask end return nil @@ -1807,8 +1911,8 @@ function OPSGROUP:RouteToMission(mission, delay) self:ScheduleOnce(delay, OPSGROUP.RouteToMission, self, mission) else - -- Next waypoint. - local nextwaypoint=self.currentwp+1 + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid -- Get coordinate where the mission is executed. local waypointcoord=mission:GetMissionWaypointCoord(self.group) @@ -1817,9 +1921,12 @@ function OPSGROUP:RouteToMission(mission, delay) for _,task in pairs(mission.enrouteTasks) do self:AddTaskEnroute(task) end + + -- Speed to mission waypoint. + local SpeedToMission=UTILS.KmphToKnots(self.speedCruise) -- Add waypoint. - local waypoint=self:AddWaypoint(waypointcoord, UTILS.KmphToKnots(self.speedCruise), nextwaypoint, false) + local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, false) -- Special for Troop transport. if mission.type==AUFTRAG.Type.TROOPTRANSPORT then @@ -1841,7 +1948,7 @@ function OPSGROUP:RouteToMission(mission, delay) end -- Add waypoint task. UpdateRoute is called inside. - local waypointtask=self:AddTaskWaypoint(mission.DCStask, nextwaypoint, mission.name, mission.prio, mission.duration) + local waypointtask=self:AddTaskWaypoint(mission.DCStask, waypoint, mission.name, mission.prio, mission.duration) -- Set waypoint task. mission:SetGroupWaypointTask(self, waypointtask) @@ -2126,6 +2233,10 @@ function OPSGROUP:onafterCheckZone(From, Event, To) end end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Internal Check Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Check if group is in zones. -- @param #OPSGROUP self function OPSGROUP:_CheckInZones() @@ -2248,6 +2359,106 @@ function OPSGROUP:_CheckDetectedUnits() end +--- Check if passed the final waypoint and, if necessary, update route. +-- @param #OPSGROUP self +-- @param #number delay Delay in seconds. +function OPSGROUP:_CheckGroupDone(delay) + + if self:IsAlive() and self.ai then + + if delay and delay>0 then + -- Delayed call. + self:ScheduleOnce(delay, self._CheckGroupDone, self) + else + + if self.passedfinalwp then + + --- + -- Passed FINAL waypoint + --- + + if #self.waypoints>1 then + + if self.adinfinitum then + + -- Get positive speed to first waypoint. + local speed=self:GetSpeedToWaypoint(1) + + -- Start route at first waypoint. + self:__UpdateRoute(-1, 1, speed) + + self:I(self.lid..string.format("Passed final WP, #WP>1, adinfinitum=TRUE ==> Goto WP 1 at speed>0")) + + self.passedfinalwp=false + + else + -- No further waypoints. Command a full stop. + self:__FullStop(-1) + + self:I(self.lid..string.format("Passed final WP, #WP>1, adinfinitum=FALSE ==> Full Stop")) + end + + elseif #self.waypoints==1 then + + --- Only one WP left + + -- The last waypoint. + local waypoint=self.waypoints[1] --Ops.OpsGroup#OPSGROUP.Waypoint + + local dist=self:GetCoordinate():Get2DDistance(waypoint.coordinate) + + + if self.adinfinitum and dist>1000 then -- Note that dist>100 caused the same wp to be passed a lot of times. + + self:I(self.lid..string.format("Passed final WP, #WP=1, adinfinitum=TRUE dist>1000 ==> Goto WP 1 at speed>0")) + + -- Get positive speed to first waypoint. + local speed=self:GetSpeedToWaypoint(1) + + -- Start route at first waypoint. + self:__UpdateRoute(-1, 1, speed) + + self.passedfinalwp=false + + else + + self:I(self.lid..string.format("Passed final WP, #WP=1, adinfinitum=FALSE or dist<1000 ==> Full Stop")) + + self:__FullStop(-1) + + end + + else + + --- No waypoints left + + -- No further waypoints. Command a full stop. + self:__FullStop(-1) + + end + + else + + --- + -- Final waypoint NOT passed yet + --- + + if #self.waypoints>0 then + self:I(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) + self:__UpdateRoute(-1) + else + self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) + self:__FullStop(-1) + end + + end + + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoints & Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2261,11 +2472,11 @@ function OPSGROUP:_CreateWaypoint(waypoint, formation, detour) waypoint.uid=self.wpcounter waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) waypoint.detour=detour and detour or false - waypoint.formation=formation if formation then waypoint.action=formation end - waypoint.onroad=onroad and onroad or false + waypoint.npassed=0 + waypoint.patrol=false self.wpcounter=self.wpcounter+1 @@ -2412,6 +2623,9 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Get the current waypoint index. opsgroup.currentwp=opsgroup:GetWaypointIndex(uid) + -- Increase passing counter. + waypoint.npassed=waypoint.npassed+1 + -- Set expected speed and formation from the next WP. local wpnext=opsgroup:GetWaypointNext() if wpnext then From cf9248ecc86512d0f4ba02aa8a24373c0cace392 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 30 Jul 2020 17:41:26 +0200 Subject: [PATCH 19/79] A* - Improved performance by caching cost and valid/invalid neighbours. --- Moose Development/Moose/Core/Astar.lua | 141 +++++++++++++++++--- Moose Development/Moose/Core/Point.lua | 40 +++--- Moose Development/Moose/Ops/NavyGroup.lua | 6 - Moose Development/Moose/Utilities/Utils.lua | 10 ++ 4 files changed, 152 insertions(+), 45 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index f4107f4f9..68ff49129 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -20,6 +20,7 @@ -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #string lid Class id string for output to DCS log file. -- @field #table nodes Table of nodes. +-- @field #number counter Node counter. -- @field #ASTAR.Node startNode Start node. -- @field #ASTAR.Node endNode End node. -- @field Core.Point#COORDINATE startCoord Start coordinate. @@ -136,12 +137,16 @@ ASTAR = { Debug = nil, lid = nil, nodes = {}, + counter = 1, } --- Node data. -- @type ASTAR.Node +-- @field #number id Node id. -- @field Core.Point#COORDINATE coordinate Coordinate of the node. -- @field #number surfacetype Surface type. +-- @field #table valid Cached valid/invalid nodes. +-- @field #table cost Cached cost. --- ASTAR infinity. -- @field #number INF @@ -149,7 +154,7 @@ ASTAR.INF=1/0 --- ASTAR class version. -- @field #string version -ASTAR.version="0.1.0" +ASTAR.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -211,6 +216,12 @@ function ASTAR:GetNodeFromCoordinate(Coordinate) node.coordinate=Coordinate node.surfacetype=Coordinate:GetSurfaceType() + node.id=self.counter + + node.valid={} + node.cost={} + + self.counter=self.counter+1 return node end @@ -518,6 +529,23 @@ function ASTAR.Dist3D(nodeA, nodeB) return nodeA.coordinate:Get3DDistance(nodeB.coordinate) end +--- Heuristic cost is given by the distance between the nodes on road. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #number Distance between the two nodes. +function ASTAR.DistRoad(nodeA, nodeB) + + local path,dist,gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate,IncludeEndpoints,Railroad,MarkPath,SmokePath) + + if gotpath then + return dist + else + return math.huge + end + + return nodeA.coordinate:Get3DDistance(nodeB.coordinate) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -616,13 +644,15 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) local text=string.format("Starting A* pathfinding") self:I(self.lid..text) MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + local Tstart=UTILS.GetOSTime() while #openset > 0 do local current=self:_LowestFscore(openset, f_score) -- Check if we are at the end node. - if current==goal then + if current.id==goal.id then local path=self:_UnwindPath({}, came_from, goal) @@ -634,8 +664,20 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) table.remove(path, 1) end + local Tstop=UTILS.GetOSTime() + + local dT=nil + if Tstart and Tstop then + dT=Tstop-Tstart + end + -- Debug message. - local text=string.format("Found path with %d nodes", #path) + local text=string.format("Found path with %d nodes (%d total nodes)", #path, #self.nodes) + if dT then + text=text..string.format(". OS Time %.6f seconds", dT) + end + text=text..string.format("\nNvalid = %d %d cached", self.nvalid, self.nvalidcache) + text=text..string.format("\nNcost = %d %d cached", self.ncost, self.ncostcache) self:I(self.lid..text) MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) @@ -689,11 +731,34 @@ end -- @return #number "Cost" to go from node A to node B. function ASTAR:_HeuristicCost(nodeA, nodeB) - if self.CostFunc then - return self.CostFunc(nodeA, nodeB, unpack(self.CostArg)) + if self.ncost then + self.ncost=self.ncost+1 else - return self:_DistNodes(nodeA, nodeB) + self.ncost=1 end + + -- Get chached cost if available. + local cost=nodeA.cost[nodeB.id] + if cost~=nil then + if self.ncostcache then + self.ncostcache=self.ncostcache+1 + else + self.ncostcache=1 + end + return cost + end + + local cost=nil + if self.CostFunc then + cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg)) + else + cost=self:_DistNodes(nodeA, nodeB) + end + + nodeA.cost[nodeB.id]=cost + nodeB.cost[nodeA.id]=cost -- Symmetric problem. + + return cost end --- Check if going from a node to a neighbour is possible. @@ -703,14 +768,34 @@ end -- @return #boolean If true, transition between nodes is possible. function ASTAR:_IsValidNeighbour(node, neighbor) - if self.ValidNeighbourFunc then - - return self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg)) - + if self.nvalid then + self.nvalid=self.nvalid+1 else - return true + self.nvalid=1 + end + + local valid=node.valid[neighbor.id] + if valid~=nil then + --env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id)) + if self.nvalidcache then + self.nvalidcache=self.nvalidcache+1 + else + self.nvalidcache=1 + end + return valid end + local valid=nil + if self.ValidNeighbourFunc then + valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg)) + else + valid=true + end + + node.valid[neighbor.id]=valid + neighbor.valid[node.id]=valid -- Symmetric problem. + + return valid end --- Calculate 2D distance between two nodes. @@ -727,7 +812,7 @@ end -- @param #table set The set of nodes. -- @param #number f_score F score. -- @return #ASTAR.Node Best node. -function ASTAR:_LowestFscore(set, f_score) +function ASTAR:_LowestFscore2(set, f_score) local lowest, bestNode = ASTAR.INF, nil @@ -743,6 +828,25 @@ function ASTAR:_LowestFscore(set, f_score) return bestNode end +--- Function that calculates the lowest F score. +-- @param #ASTAR self +-- @param #table set The set of nodes. +-- @param #number f_score F score. +-- @return #ASTAR.Node Best node. +function ASTAR:_LowestFscore(set, f_score) + + local function sort(A, B) + local a=A --#ASTAR.Node + local b=B --#ASTAR.Node + return f_score[a]=2 then for i=1,#Path-1 do @@ -1562,7 +1560,7 @@ do -- COORDINATE end else -- There are cases where no path on road can be found. - return nil,nil + return nil,nil,false end return Path, Way, GotPath diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index fbd4fa740..bb6a31ccd 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1535,12 +1535,6 @@ function NAVYGROUP:_FindPathToNextWaypoint() for i,_node in ipairs(path) do local node=_node --Core.Astar#ASTAR.Node - - -- Waypoint index. - local wpindex=self:GetWaypointIndexCurrent()+i - - -- ID of current waypoint. - local uid=self:GetWaypointCurrent().uid -- Add waypoints along detour path to next waypoint. local wp=self:AddWaypoint(node.coordinate, speed, uid) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 74a7b4c5b..2565d397b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1400,4 +1400,14 @@ function UTILS.GetSunset(Day, Month, Year, Latitude, Longitude, Tlocal) local DayOfYear=UTILS.GetDayOfYear(Year, Month, Day) return UTILS.GetSunRiseAndSet(DayOfYear, Latitude, Longitude, false, Tlocal) +end + +--- Get OS time. Needs os to be desanitized! +-- @return #number Os time in seconds. +function UTILS.GetOSTime() + if os then + return os.clock() + end + + return nil end \ No newline at end of file From ba90176bb1173cc29564b7a6f6808660205a2ed1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 30 Jul 2020 21:40:27 +0200 Subject: [PATCH 20/79] A* Further optimized by using DCS API land.isVisible instead of Moose IsLoS function. --- Moose Development/Moose/Core/Astar - Copy.lua | 932 ++++++++++++++++++ Moose Development/Moose/Core/Astar.lua | 28 +- Moose Development/Moose/Ops/Squadron.lua | 2 +- Moose Development/Moose/Utilities/Utils.lua | 17 +- 4 files changed, 966 insertions(+), 13 deletions(-) create mode 100644 Moose Development/Moose/Core/Astar - Copy.lua diff --git a/Moose Development/Moose/Core/Astar - Copy.lua b/Moose Development/Moose/Core/Astar - Copy.lua new file mode 100644 index 000000000..de590b07c --- /dev/null +++ b/Moose Development/Moose/Core/Astar - Copy.lua @@ -0,0 +1,932 @@ +--- **Core** - A* Pathfinding. +-- +-- **Main Features:** +-- +-- * Find path from A to B. +-- * Pre-defined as well as custom valid neighbour functions. +-- * Pre-defined as well as custom cost functions. +-- * Easy rectangular grid setup. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Core.Astar +-- @image CORE_Astar.png + + +--- ASTAR class. +-- @type ASTAR +-- @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 #table nodes Table of nodes. +-- @field #number counter Node counter. +-- @field #ASTAR.Node startNode Start node. +-- @field #ASTAR.Node endNode End node. +-- @field Core.Point#COORDINATE startCoord Start coordinate. +-- @field Core.Point#COORDINATE endCoord End coordinate. +-- @field #function ValidNeighbourFunc Function to check if a node is valid. +-- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function. +-- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another. +-- @field #table CostArg Optional arguments passed to the cost function. +-- @extends Core.Base#BASE + +--- When nothing goes right... Go left! +-- +-- === +-- +-- ![Banner Image](..\Presentations\Astar\ASTAR_Main.jpg) +-- +-- # The ASTAR Concept +-- +-- Pathfinding algorithm. +-- +-- +-- # Start and Goal +-- +-- The first thing we need to define is obviously the place where we want to start and where we want to go eventually. +-- +-- ## Start +-- +-- The start +-- +-- ## Goal +-- +-- +-- # Nodes +-- +-- ## Rectangular Grid +-- +-- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where +-- +-- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid. +-- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km). +-- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km). +-- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km). +-- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*. +-- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created. +-- +-- ## Valid Surfaces +-- +-- Certain unit types can only travel on certain surfaces types, for example +-- +-- * Naval units can only travel on water (that also excludes shallow water in DCS currently), +-- * Ground units can only traval on land. +-- +-- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient. +-- +-- ## Box Width (BoxHY) +-- +-- The box width needs to be large enough to capture all paths you want to consider. +-- +-- ## Space in X +-- +-- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node. +-- +-- ## Grid Spacing +-- +-- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible. +-- However, if the value is too large, the algorithm might fail to get a valid path. +-- +-- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path. +-- +-- # Valid Neighbours +-- +-- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible. +-- +-- ## Line of Sight +-- +-- For naval +-- +-- +-- # Heuristic Cost +-- +-- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another. +-- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes. +-- +-- +-- # Calculate the Path +-- +-- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which +-- describe the optimal path from the start node to the end node. +-- +-- By default, the start and end node are include in the table that is returned. +-- +-- Note that a valid path must not always exist. So you should check if the function returns *nil*. +-- +-- Common reasons that a path cannot be found are: +-- +-- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid. +-- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY* +-- * There simply is no valid path ==> you are screwed :( +-- +-- +-- # Examples +-- +-- ## Strait of Hormuz +-- +-- Carrier Group finds its way through the Stait of Hormuz. +-- +-- ## +-- +-- +-- +-- @field #ASTAR +ASTAR = { + ClassName = "ASTAR", + Debug = nil, + lid = nil, + nodes = {}, + counter = 1, +} + +--- Node data. +-- @type ASTAR.Node +-- @field #number id Node id. +-- @field Core.Point#COORDINATE coordinate Coordinate of the node. +-- @field #number surfacetype Surface type. +-- @field #table valid Cached valid/invalid nodes. +-- @field #table cost Cached cost. + +--- ASTAR infinity. +-- @field #number INF +ASTAR.INF=1/0 + +--- ASTAR class version. +-- @field #string version +ASTAR.version="0.3.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Add more valid neighbour functions. +-- TODO: Write docs. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new ASTAR object. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:New() + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, BASE:New()) --#ASTAR + + self.lid="ASTAR | " + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set coordinate from where to start. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate Start coordinate. +-- @return #ASTAR self +function ASTAR:SetStartCoordinate(Coordinate) + + self.startCoord=Coordinate + + return self +end + +--- Set coordinate where you want to go. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate end coordinate. +-- @return #ASTAR self +function ASTAR:SetEndCoordinate(Coordinate) + + self.endCoord=Coordinate + + return self +end + +--- Create a node from a given coordinate. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node. +-- @return #ASTAR.Node The node. +function ASTAR:GetNodeFromCoordinate(Coordinate) + + local node={} --#ASTAR.Node + + node.coordinate=Coordinate + node.surfacetype=Coordinate:GetSurfaceType() + node.id=self.counter + + node.valid={} + node.cost={} + + self.counter=self.counter+1 + + return node +end + + +--- Add a node to the table of grid nodes. +-- @param #ASTAR self +-- @param #ASTAR.Node Node The node to be added. +-- @return #ASTAR self +function ASTAR:AddNode(Node) + + table.insert(self.nodes, Node) + + return self +end + +--- Add a node to the table of grid nodes specifying its coordinate. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created. +-- @return #ASTAR.Node The node. +function ASTAR:AddNodeFromCoordinate(Coordinate) + + local node=self:GetNodeFromCoordinate(Coordinate) + + self:AddNode(node) + + return node +end + +--- Check if the coordinate of a node has is at a valid surface type. +-- @param #ASTAR self +-- @param #ASTAR.Node Node The node to be added. +-- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid. +-- @return #boolean If true, surface type of node is valid. +function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes) + + if SurfaceTypes then + + if type(SurfaceTypes)~="table" then + SurfaceTypes={SurfaceTypes} + end + + for _,surface in pairs(SurfaceTypes) do + if surface==Node.surfacetype then + return true + end + end + + return false + + else + return true + end + +end + +--- Add a function to determine if a neighbour of a node is valid. +-- @param #ASTAR self +-- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid. +-- @param ... Condition function arguments if any. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...) + + self.ValidNeighbourFunc=NeighbourFunction + + self.ValidNeighbourArg={} + if arg then + self.ValidNeighbourArg=arg + end + + return self +end + + +--- Set valid neighbours to require line of sight between two nodes. +-- @param #ASTAR self +-- @param #number CorridorWidth Width of LoS corridor in meters. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourLoS(CorridorWidth) + + self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth) + + return self +end + +--- Set valid neighbours to be in a certain distance. +-- @param #ASTAR self +-- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m. +-- @return #ASTAR self +function ASTAR:SetValidNeighbourDistance(MaxDistance) + + self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance) + + return self +end + +--- Set the function which calculates the "cost" to go from one to another node. +-- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments. +-- Very often the distance between nodes is a good measure for the cost. +-- @param #ASTAR self +-- @param #function CostFunction Function that returns the "cost". +-- @param ... Condition function arguments if any. +-- @return #ASTAR self +function ASTAR:SetCostFunction(CostFunction, ...) + + self.CostFunc=CostFunction + + self.CostArg={} + if arg then + self.CostArg=arg + end + + return self +end + +--- Set heuristic cost to go from one node to another to be their 2D distance. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:SetCostDist2D() + + self:SetCostFunction(ASTAR.Dist2D) + + return self +end + +--- Set heuristic cost to go from one node to another to be their 3D distance. +-- @param #ASTAR self +-- @return #ASTAR self +function ASTAR:SetCostDist3D() + + self:SetCostFunction(ASTAR.Dist3D) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Grid functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a rectangular grid of nodes between star and end coordinate. +-- The coordinate system is oriented along the line between start and end point. +-- @param #ASTAR self +-- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed. +-- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km). +-- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km). +-- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters. +-- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX. +-- @param #boolean MarkGrid If true, create F10 map markers at grid nodes. +-- @return #ASTAR self +function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid) + + -- Note that internally + -- x coordinate is z: x-->z Line from start to end + -- y coordinate is x: y-->x Perpendicular + + -- Grid length and width. + local Dz=SpaceX or 10000 + local Dx=BoxHY and BoxHY/2 or 20000 + + -- Increments. + local dz=deltaX or 2000 + local dx=deltaY or dz + + -- Heading from start to end coordinate. + local angle=self.startCoord:HeadingTo(self.endCoord) + + --Distance between start and end. + local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz + + -- Origin of map. Needed to translate back to wanted position. + local co=COORDINATE:New(0, 0, 0) + local do1=co:Get2DDistance(self.startCoord) + local ho1=co:HeadingTo(self.startCoord) + + -- Start of grid. + local xmin=-Dx + local zmin=-Dz + + -- Number of grid points. + local nz=dist/dz+1 + local nx=2*Dx/dx+1 + + -- Debug info. + local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz) + self:I(self.lid..text) + MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + + -- Loop over x and z coordinate to create a 2D grid. + for i=1,nx do + + -- x coordinate perpendicular to z. + local x=xmin+dx*(i-1) + + for j=1,nz do + + -- z coordinate connecting start and end. + local z=zmin+dz*(j-1) + + -- Rotate 2D. + local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle) + + -- Coordinate of the node. + local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true) + + -- Create a node at this coordinate. + local node=self:GetNodeFromCoordinate(c) + + -- Check if node has valid surface type. + if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then + + if MarkGrid then + c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype)) + end + + -- Add node to grid. + self:AddNode(node) + + end + + end + end + + -- Debug info. + local text=string.format("Done building grid!") + self:I(self.lid..text) + MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Valid neighbour functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function to check if two nodes have line of sight (LoS). +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @param #number corridor (Optional) Width of corridor in meters. +-- @return #boolean If true, two nodes have LoS. +function ASTAR.LoS(nodeA, nodeB, corridor) + + local offset=0.1 + + local dx=corridor and corridor/2 or nil + local dy=dx + + local cA=nodeA.coordinate:GetVec3() + local cB=nodeB.coordinate:GetVec3() + cA.y=offset + cB.y=offset + + local los=land.isVisible(cA, cB) + + if los and corridor then + + -- Heading from A to B. + local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) + + local Ap=UTILS.VecTranslate(cA, dx, heading+90) + local Bp=UTILS.VecTranslate(cB, dx, heading+90) + + los=land.isVisible(Ap, Bp) --Ap:IsLOS(Bp, offset) + + if los then + + local Am=UTILS.VecTranslate(cA, dx, heading-90) + local Bm=UTILS.VecTranslate(cB, dx, heading-90) + + los=land.isVisible(Am, Bm) + end + + end + + return los +end + +--- Function to check if two nodes have line of sight (LoS). +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @param #number distmax Max distance in meters. Default is 2000 m. +-- @return #boolean If true, distance between the two nodes is below threshold. +function ASTAR.DistMax(nodeA, nodeB, distmax) + + distmax=distmax or 2000 + + local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate) + + return dist<=distmax +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Heuristic cost functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Heuristic cost is given by the 2D distance between the nodes. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #number Distance between the two nodes. +function ASTAR.Dist2D(nodeA, nodeB) + return nodeA.coordinate:Get2DDistance(nodeB) +end + +--- Heuristic cost is given by the 3D distance between the nodes. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #number Distance between the two nodes. +function ASTAR.Dist3D(nodeA, nodeB) + return nodeA.coordinate:Get3DDistance(nodeB.coordinate) +end + +--- Heuristic cost is given by the distance between the nodes on road. +-- @param #ASTAR.Node nodeA First node. +-- @param #ASTAR.Node nodeB Other node. +-- @return #number Distance between the two nodes. +function ASTAR.DistRoad(nodeA, nodeB) + + local path,dist,gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate,IncludeEndpoints,Railroad,MarkPath,SmokePath) + + if gotpath then + return dist + else + return math.huge + end + + return nodeA.coordinate:Get3DDistance(nodeB.coordinate) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + +--- Find the closest node from a given coordinate. +-- @param #ASTAR self +-- @param Core.Point#COORDINATE Coordinate. +-- @return #ASTAR.Node Cloest node to the coordinate. +-- @return #number Distance to closest node in meters. +function ASTAR:FindClosestNode(Coordinate) + + local distMin=math.huge + local closeNode=nil + + for _,_node in pairs(self.nodes) do + local node=_node --#ASTAR.Node + + local dist=node.coordinate:Get2DDistance(Coordinate) + + if dist1000 then + self:I(self.lid.."Adding start node to node grid!") + self:AddNode(node) + end + + return self +end + +--- Add a node. +-- @param #ASTAR self +-- @param #ASTAR.Node Node The node to be added to the nodes table. +-- @return #ASTAR self +function ASTAR:FindEndNode() + + local node, dist=self:FindClosestNode(self.endCoord) + + self.endNode=node + + if dist>1000 then + self:I(self.lid.."Adding end node to node grid!") + self:AddNode(node) + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Main A* pathfinding function +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates. +-- @param #ASTAR self +-- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it. +-- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it. +-- @return #table Table of nodes from start to finish. +function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) + + self:FindStartNode() + self:FindEndNode() + + local nodes=self.nodes + local start=self.startNode + local goal=self.endNode + + local closedset = {} + local openset = { start } + local came_from = {} + + local g_score, f_score = {}, {} + + g_score[start]=0 + f_score[start]=g_score[start]+self:_HeuristicCost(start, goal) + + -- Set start time. + local T0=timer.getAbsTime() + + -- Debug message. + local text=string.format("Starting A* pathfinding") + self:I(self.lid..text) + MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) + + local Tstart=UTILS.GetOSTime() + + while #openset > 0 do + + local current=self:_LowestFscore(openset, f_score) + + -- Check if we are at the end node. + if current.id==goal.id then + + local path=self:_UnwindPath({}, came_from, goal) + + if not ExcludeEndNode then + table.insert(path, goal) + end + + if ExcludeStartNode then + table.remove(path, 1) + end + + local Tstop=UTILS.GetOSTime() + + local dT=nil + if Tstart and Tstop then + dT=Tstop-Tstart + end + + -- Debug message. + local text=string.format("Found path with %d nodes (%d total nodes)", #path, #self.nodes) + if dT then + text=text..string.format(". OS Time %.6f seconds", dT) + end + text=text..string.format("\nNvalid = %d %d cached", self.nvalid, self.nvalidcache) + text=text..string.format("\nNcost = %d %d cached", self.ncost, self.ncostcache) + self:I(self.lid..text) + MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) + + return path + end + + self:_RemoveNode(openset, current) + table.insert(closedset, current) + + local neighbors=self:_NeighbourNodes(current, nodes) + + -- Loop over neighbours. + for _,neighbor in ipairs(neighbors) do + + if self:_NotIn(closedset, neighbor) then + + local tentative_g_score=g_score[current]+self:_DistNodes(current, neighbor) + + if self:_NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then + + came_from[neighbor]=current + + g_score[neighbor]=tentative_g_score + f_score[neighbor]=g_score[neighbor]+self:_HeuristicCost(neighbor, goal) + + if self:_NotIn(openset, neighbor) then + table.insert(openset, neighbor) + end + + end + end + end + end + + -- Debug message. + local text=string.format("WARNING: Could NOT find valid path!") + self:E(self.lid..text) + MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) + + return nil -- no valid path +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- A* pathfinding helper functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes. +-- @param #ASTAR self +-- @param #ASTAR.Node nodeA Node A. +-- @param #ASTAR.Node nodeB Node B. +-- @return #number "Cost" to go from node A to node B. +function ASTAR:_HeuristicCost(nodeA, nodeB) + + if self.ncost then + self.ncost=self.ncost+1 + else + self.ncost=1 + end + + -- Get chached cost if available. + local cost=nodeA.cost[nodeB.id] + if cost~=nil then + if self.ncostcache then + self.ncostcache=self.ncostcache+1 + else + self.ncostcache=1 + end + return cost + end + + local cost=nil + if self.CostFunc then + cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg)) + else + cost=self:_DistNodes(nodeA, nodeB) + end + + nodeA.cost[nodeB.id]=cost + nodeB.cost[nodeA.id]=cost -- Symmetric problem. + + return cost +end + +--- Check if going from a node to a neighbour is possible. +-- @param #ASTAR self +-- @param #ASTAR.Node node A node. +-- @param #ASTAR.Node neighbor Neighbour node. +-- @return #boolean If true, transition between nodes is possible. +function ASTAR:_IsValidNeighbour(node, neighbor) + + if self.nvalid then + self.nvalid=self.nvalid+1 + else + self.nvalid=1 + end + + local valid=node.valid[neighbor.id] + if valid~=nil then + --env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id)) + if self.nvalidcache then + self.nvalidcache=self.nvalidcache+1 + else + self.nvalidcache=1 + end + return valid + end + + local valid=nil + if self.ValidNeighbourFunc then + valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg)) + else + valid=true + end + + node.valid[neighbor.id]=valid + neighbor.valid[node.id]=valid -- Symmetric problem. + + return valid +end + +--- Calculate 2D distance between two nodes. +-- @param #ASTAR self +-- @param #ASTAR.Node nodeA Node A. +-- @param #ASTAR.Node nodeB Node B. +-- @return #number Distance between nodes in meters. +function ASTAR:_DistNodes(nodeA, nodeB) + return nodeA.coordinate:Get2DDistance(nodeB.coordinate) +end + +--- Function that calculates the lowest F score. +-- @param #ASTAR self +-- @param #table set The set of nodes. +-- @param #number f_score F score. +-- @return #ASTAR.Node Best node. +function ASTAR:_LowestFscore2(set, f_score) + + local lowest, bestNode = ASTAR.INF, nil + + for _, node in ipairs ( set ) do + + local score = f_score [ node ] + + if score < lowest then + lowest, bestNode = score, node + end + end + + return bestNode +end + +--- Function that calculates the lowest F score. +-- @param #ASTAR self +-- @param #table set The set of nodes. +-- @param #number f_score F score. +-- @return #ASTAR.Node Best node. +function ASTAR:_LowestFscore(set, f_score) + + local function sort(A, B) + local a=A --#ASTAR.Node + local b=B --#ASTAR.Node + return f_score[a]1000 then + self:I(self.lid.."Adding start node to node grid!") self:AddNode(node) end @@ -604,6 +609,7 @@ function ASTAR:FindEndNode() self.endNode=node if dist>1000 then + self:I(self.lid.."Adding end node to node grid!") self:AddNode(node) end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 5b94d81aa..96b4d8655 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -641,7 +641,7 @@ function SQUADRON:CanMission(Mission) return true end ---- Get assets for a mission. +--- Count assets in airwing (warehous) stock. -- @param #SQUADRON self -- @return #number Assets not spawned. function SQUADRON:CountAssetsInStock() diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 2565d397b..1970e0c17 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -969,6 +969,22 @@ function UTILS.HdgDiff(h1, h2) end +--- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. +-- @param DCS#Vec3 a Vector in 3D with x, y, z components. +-- @param #number distance The distance to translate. +-- @param #number angle Rotation angle in degrees. +-- @return DCS#Vec3 Vector rotated in the (x,z) plane. +function UTILS.VecTranslate(a, distance, angle) + + local SX = a.x + local SY = a.z + local Radians=math.rad(angle or 0) + local TX=distance*math.cos(Radians)+SX + local TY=distance*math.sin(Radians)+SY + + return {x=TX, y=a.y, z=TY} +end + --- Rotate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. -- @param #number angle Rotation angle in degrees. @@ -990,7 +1006,6 @@ function UTILS.Rotate2D(a, angle) end - --- Converts a TACAN Channel/Mode couple into a frequency in Hz. -- @param #number TACANChannel The TACAN channel, i.e. the 10 in "10X". -- @param #string TACANMode The TACAN mode, i.e. the "X" in "10X". From da2aa004426f37bd84f891e1b14646223e63dae1 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 31 Jul 2020 00:09:21 +0200 Subject: [PATCH 21/79] A* Further improvements by switching to keys. --- Moose Development/Moose/Core/Astar.lua | 177 +++++++++------------- Moose Development/Moose/Ops/ArmyGroup.lua | 9 ++ 2 files changed, 81 insertions(+), 105 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index de590b07c..544d18697 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -21,6 +21,11 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #table nodes Table of nodes. -- @field #number counter Node counter. +-- @field #number Nnodes Number of nodes. +-- @field #number nvalid Number of nvalid calls. +-- @field #number nvalidcache Number of cached valid evals. +-- @field #number ncost Number of cost evaluations. +-- @field #number ncostcache Number of cached cost evals. -- @field #ASTAR.Node startNode Start node. -- @field #ASTAR.Node endNode End node. -- @field Core.Point#COORDINATE startCoord Start coordinate. @@ -138,6 +143,11 @@ ASTAR = { lid = nil, nodes = {}, counter = 1, + Nnodes = 0, + ncost = 0, + ncostcache = 0, + nvalid = 0, + nvalidcache = 0, } --- Node data. @@ -154,7 +164,7 @@ ASTAR.INF=1/0 --- ASTAR class version. -- @field #string version -ASTAR.version="0.3.0" +ASTAR.version="0.4.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -233,7 +243,8 @@ end -- @return #ASTAR self function ASTAR:AddNode(Node) - table.insert(self.nodes, Node) + self.nodes[Node.id]=Node + self.Nnodes=self.Nnodes+1 return self end @@ -484,7 +495,7 @@ function ASTAR.LoS(nodeA, nodeB, corridor) local Ap=UTILS.VecTranslate(cA, dx, heading+90) local Bp=UTILS.VecTranslate(cB, dx, heading+90) - los=land.isVisible(Ap, Bp) --Ap:IsLOS(Bp, offset) + los=land.isVisible(Ap, Bp) if los then @@ -634,27 +645,34 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) local start=self.startNode local goal=self.endNode + -- Sets. + local openset = {} local closedset = {} - local openset = { start } local came_from = {} - - local g_score, f_score = {}, {} + local g_score = {} + local f_score = {} - g_score[start]=0 - f_score[start]=g_score[start]+self:_HeuristicCost(start, goal) + openset[start.id]=true + local Nopen=1 + + -- Initial scores. + g_score[start.id]=0 + f_score[start.id]=g_score[start.id]+self:_HeuristicCost(start, goal) -- Set start time. local T0=timer.getAbsTime() -- Debug message. - local text=string.format("Starting A* pathfinding") + local text=string.format("Starting A* pathfinding with %d Nodes", self.Nnodes) self:I(self.lid..text) MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) local Tstart=UTILS.GetOSTime() - while #openset > 0 do + -- Loop while we still have an open set. + while Nopen > 0 do + -- Get current node. local current=self:_LowestFscore(openset, f_score) -- Check if we are at the end node. @@ -678,39 +696,44 @@ function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) end -- Debug message. - local text=string.format("Found path with %d nodes (%d total nodes)", #path, #self.nodes) + local text=string.format("Found path with %d nodes (%d total)", #path, self.Nnodes) if dT then - text=text..string.format(". OS Time %.6f seconds", dT) + text=text..string.format(", OS Time %.6f sec", dT) end - text=text..string.format("\nNvalid = %d %d cached", self.nvalid, self.nvalidcache) - text=text..string.format("\nNcost = %d %d cached", self.ncost, self.ncostcache) + text=text..string.format(", Nvalid=%d [%d cached]", self.nvalid, self.nvalidcache) + text=text..string.format(", Ncost=%d [%d cached]", self.ncost, self.ncostcache) self:I(self.lid..text) MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) return path end - self:_RemoveNode(openset, current) - table.insert(closedset, current) + -- Move Node from open to closed set. + openset[current.id]=nil + Nopen=Nopen-1 + closedset[current.id]=true + -- Get neighbour nodes. local neighbors=self:_NeighbourNodes(current, nodes) -- Loop over neighbours. - for _,neighbor in ipairs(neighbors) do + for _,neighbor in pairs(neighbors) do - if self:_NotIn(closedset, neighbor) then + if self:_NotIn(closedset, neighbor.id) then - local tentative_g_score=g_score[current]+self:_DistNodes(current, neighbor) + local tentative_g_score=g_score[current.id]+self:_DistNodes(current, neighbor) - if self:_NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then + if self:_NotIn(openset, neighbor.id) or tentative_g_score < g_score[neighbor.id] then came_from[neighbor]=current - g_score[neighbor]=tentative_g_score - f_score[neighbor]=g_score[neighbor]+self:_HeuristicCost(neighbor, goal) + g_score[neighbor.id]=tentative_g_score + f_score[neighbor.id]=g_score[neighbor.id]+self:_HeuristicCost(neighbor, goal) - if self:_NotIn(openset, neighbor) then - table.insert(openset, neighbor) + if self:_NotIn(openset, neighbor.id) then + -- Add to open set. + openset[neighbor.id]=true + Nopen=Nopen+1 end end @@ -736,21 +759,14 @@ end -- @param #ASTAR.Node nodeB Node B. -- @return #number "Cost" to go from node A to node B. function ASTAR:_HeuristicCost(nodeA, nodeB) - - if self.ncost then - self.ncost=self.ncost+1 - else - self.ncost=1 - end + + -- Counter. + self.ncost=self.ncost+1 -- Get chached cost if available. local cost=nodeA.cost[nodeB.id] if cost~=nil then - if self.ncostcache then - self.ncostcache=self.ncostcache+1 - else - self.ncostcache=1 - end + self.ncostcache=self.ncostcache+1 return cost end @@ -774,20 +790,13 @@ end -- @return #boolean If true, transition between nodes is possible. function ASTAR:_IsValidNeighbour(node, neighbor) - if self.nvalid then - self.nvalid=self.nvalid+1 - else - self.nvalid=1 - end + -- Counter. + self.nvalid=self.nvalid+1 local valid=node.valid[neighbor.id] if valid~=nil then --env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id)) - if self.nvalidcache then - self.nvalidcache=self.nvalidcache+1 - else - self.nvalidcache=1 - end + self.nvalidcache=self.nvalidcache+1 return valid end @@ -815,44 +824,25 @@ end --- Function that calculates the lowest F score. -- @param #ASTAR self --- @param #table set The set of nodes. --- @param #number f_score F score. --- @return #ASTAR.Node Best node. -function ASTAR:_LowestFscore2(set, f_score) - - local lowest, bestNode = ASTAR.INF, nil - - for _, node in ipairs ( set ) do - - local score = f_score [ node ] - - if score < lowest then - lowest, bestNode = score, node - end - end - - return bestNode -end - ---- Function that calculates the lowest F score. --- @param #ASTAR self --- @param #table set The set of nodes. +-- @param #table set The set of nodes IDs. -- @param #number f_score F score. -- @return #ASTAR.Node Best node. function ASTAR:_LowestFscore(set, f_score) - local function sort(A, B) - local a=A --#ASTAR.Node - local b=B --#ASTAR.Node - return f_score[a] Date: Fri, 31 Jul 2020 17:32:24 +0200 Subject: [PATCH 22/79] Profiler --- Moose Development/Moose/Core/Astar.lua | 7 +- Moose Development/Moose/Ops/ArmyGroup.lua | 15 +- Moose Development/Moose/Ops/Auftrag.lua | 4 +- Moose Development/Moose/Ops/FlightGroup.lua | 201 ----------- Moose Development/Moose/Ops/NavyGroup.lua | 6 + Moose Development/Moose/Ops/OpsGroup.lua | 327 +++++++++++++++--- .../Moose/Utilities/Profiler.lua | 295 ++++++++++++++++ 7 files changed, 600 insertions(+), 255 deletions(-) create mode 100644 Moose Development/Moose/Utilities/Profiler.lua diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 544d18697..59a199e62 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -36,7 +36,7 @@ -- @field #table CostArg Optional arguments passed to the cost function. -- @extends Core.Base#BASE ---- When nothing goes right... Go left! +--- **When nothing goes right... Go left!** -- -- === -- @@ -510,7 +510,7 @@ function ASTAR.LoS(nodeA, nodeB, corridor) return los end ---- Function to check if two nodes have line of sight (LoS). +--- Function to check if distance between two nodes is less than a threshold distance. -- @param #ASTAR.Node nodeA First node. -- @param #ASTAR.Node nodeB Other node. -- @param #number distmax Max distance in meters. Default is 2000 m. @@ -550,7 +550,7 @@ end -- @return #number Distance between the two nodes. function ASTAR.DistRoad(nodeA, nodeB) - local path,dist,gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate,IncludeEndpoints,Railroad,MarkPath,SmokePath) + local path, dist, gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate,IncludeEndpoints,Railroad,MarkPath,SmokePath) if gotpath then return dist @@ -565,7 +565,6 @@ end -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Find the closest node from a given coordinate. -- @param #ASTAR self -- @param Core.Point#COORDINATE Coordinate. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index b7ce3e4a0..b8db518b0 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -349,10 +349,10 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if self.ai then -- Set default ROE option. - self:SetOptionROE(self.roe) + self:SwitchROE(self.option.ROE) -- Set default Alarm State option. - self:SetOptionAlarmstate(self.alarmstate) + self:SwitchAlarmstate(self.option.Alarm) end @@ -408,7 +408,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) end -- Current set formation. - self.formation=wp.action + self.option.Formation=wp.action -- Current set speed in m/s. self.speed=wp.speed @@ -436,14 +436,14 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Current waypoint. - local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speed), self.formation) + local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speed), self.option.Formation) table.insert(waypoints, 1, current) table.insert(waypoints, 1, current) -- Seems to be better to add this twice. Otherwise, the passing waypoint functions is triggered to early! if #waypoints>2 then self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Formation=%s", - self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speed), tostring(self.formation))) + self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speed), tostring(self.option.Formation))) -- Route group to all defined waypoints remaining. self:Route(waypoints) @@ -829,6 +829,11 @@ function ARMYGROUP:_InitGroup() return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Option Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 143b0f9e1..7c25a4a1d 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1362,7 +1362,7 @@ function AUFTRAG:SetEngageAsGroup(Switch) return self end ---- Set engage altitude. +--- Set engage altitude. This is the altitude passed to the DCS task. In the ME it is the tickbox ALTITUDE ABOVE. -- @param #AUFTRAG self -- @param #string Altitude Altitude in feet. Default 6000 ft. -- @return #AUFTRAG self @@ -1376,7 +1376,7 @@ function AUFTRAG:SetEngageAltitude(Altitude) return self end ---- Set mission altitude. +--- Set mission altitude. This is the altitude of the waypoint create where the DCS task is executed. -- @param #AUFTRAG self -- @param #string Altitude Altitude in feet. -- @return #AUFTRAG self diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 940da0b03..cd3087562 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -3513,208 +3513,7 @@ end -- OPTION FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set default TACAN parameters. AA TACANs are always on "Y" band. --- @param #FLIGHTGROUP self --- @param #number Channel TACAN channel. --- @param #string Morse Morse code. Default "XXX". --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetDefaultTACAN(Channel, Morse) - self.tacanChannelDefault=Channel - self.tacanMorseDefault=Morse or "XXX" - - return self -end - ---- Activate TACAN beacon. --- @param #FLIGHTGROUP self --- @param #number TACANChannel TACAN Channel. --- @param #string TACANMorse TACAN morse code. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SwitchTACANOn(TACANChannel, TACANMorse) - - if self:IsAlive() then - - local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT - - if unit and unit:IsAlive() then - - local Type=4 - local System=5 - local UnitID=unit:GetID() - local TACANMode="Y" - local Frequency=UTILS.TACANToFrequency(TACANChannel, TACANMode) - - unit:CommandActivateBeacon(Type, System, Frequency, UnitID, TACANChannel, TACANMode, true, TACANMorse, true) - - self.tacanBeacon=unit - self.tacanChannel=TACANChannel - self.tacanMorse=TACANMorse - - self.tacanOn=true - - self:I(self.lid..string.format("Switching TACAN to Channel %dY Morse %s", self.tacanChannel, tostring(self.tacanMorse))) - - end - - end - - return self -end - ---- Deactivate TACAN beacon. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SwitchTACANOff() - - if self.tacanBeacon and self.tacanBeacon:IsAlive() then - self.tacanBeacon:CommandDeactivateBeacon() - end - - self:I(self.lid..string.format("Switching TACAN OFF")) - - self.tacanOn=false - -end - ---- Set default Radio frequency and modulation. --- @param #FLIGHTGROUP self --- @param #number Frequency Radio frequency in MHz. Default 251 MHz. --- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetDefaultRadio(Frequency, Modulation) - - self.radioFreqDefault=Frequency or 251 - self.radioModuDefault=Modulation or radio.modulation.AM - - self.radioOn=false - - self.radioUse=true - - return self -end - ---- Get current Radio frequency and modulation. --- @param #FLIGHTGROUP self --- @return #number Radio frequency in MHz or nil. --- @return #number Radio modulation or nil. -function FLIGHTGROUP:GetRadio() - return self.radioFreq, self.radioModu -end - ---- Turn radio on. --- @param #FLIGHTGROUP self --- @param #number Frequency Radio frequency in MHz. --- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SwitchRadioOn(Frequency, Modulation) - - if self:IsAlive() and Frequency then - - Modulation=Modulation or radio.Modulation.AM - - local group=self.group --Wrapper.Group#GROUP - - group:SetOption(AI.Option.Air.id.SILENCE, false) - - group:CommandSetFrequency(Frequency, Modulation) - - self.radioFreq=Frequency - self.radioModu=Modulation - self.radioOn=true - - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radioFreq, UTILS.GetModulationName(self.radioModu))) - - end - - return self -end - ---- Turn radio off. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SwitchRadioOff() - - if self:IsAlive() then - - self.group:SetOption(AI.Option.Air.id.SILENCE, true) - - self.radioFreq=nil - self.radioModu=nil - self.radioOn=false - - self:I(self.lid..string.format("Switching radio OFF")) - - end - - return self -end - ---- Set default formation. --- @param #FLIGHTGROUP self --- @param #number Formation The formation the groups flies in. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetDefaultFormation(Formation) - - self.formationDefault=Formation - - return self -end - ---- Switch to a specific formation. --- @param #FLIGHTGROUP self --- @param #number Formation New formation the group will fly in. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SwitchFormation(Formation) - - if self:IsAlive() and Formation then - - self.group:SetOption(AI.Option.Air.id.FORMATION, Formation) - - self.formation=Formation - - self:I(self.lid..string.format("Switching formation to %d", self.formation)) - - end - - return self -end - ---- Set default formation. --- @param #FLIGHTGROUP self --- @param #number CallsignName Callsign name. --- @param #number CallsignNumber Callsign number. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) - - self.callsignNameDefault=CallsignName - self.callsignNumberDefault=CallsignNumber or 1 - - return self -end - ---- Switch to a specific callsign. --- @param #FLIGHTGROUP self --- @param #number CallsignName Callsign name. --- @param #number CallsignNumber Callsign number. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SwitchCallsign(CallsignName, CallsignNumber) - - if self:IsAlive() and CallsignName then - - self.callsignName=CallsignName - self.callsignNumber=CallsignNumber or 1 - - self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsignName, self.callsignNumber)) - - local group=self.group --Wrapper.Group#GROUP - - group:CommandSetCallsign(self.callsignName, self.callsignNumber) - - end - - return self -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index bb6a31ccd..048aac73f 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1152,6 +1152,12 @@ function NAVYGROUP:_InitGroup() return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Option Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 9587aa307..894d8ecf8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -54,34 +54,22 @@ -- @field #number traveltime Time. -- @field #boolean ispathfinding If true, group is on pathfinding route. -- --- @field #number tacanChannelDefault The default TACAN channel. --- @field #string tacanMorseDefault The default TACAN morse code. --- @field #number tacanChannel The currenly used TACAN channel. --- @field #string tacanMorse The currently used TACAN morse code. --- @field #boolean tacanOn If true, TACAN is currently active. --- @field Wrapper.Unit#UNIT tacanBeacon The unit acting as TACAN beacon. -- --- @field #number radioFreqDefault Default radio frequency in MHz. --- @field #number radioFreq Currently used radio frequency in MHz. --- @field #number radioModuDefault Default Radio modulation `radio.modulation.AM` or `radio.modulation.FM`. --- @field #number radioModu Currently used radio modulation `radio.modulation.AM` or `radio.modulation.FM`. +-- @field #OPSGROUP.Radio radio Current radio settings. +-- @field #OPSGROUP.Radio radioDefault Default radio settings. -- @field #boolean radioOn If true, radio is currently turned on. -- @field Core.RadioQueue#RADIOQUEUE radioQueue Radio queue. -- --- @field #boolean eplrsDefault Default EPLRS data link setting. --- @field #boolean eplrs If true, EPLRS data link is on. +-- @field #OPSGROUP.Beacon tacan Current TACAN settings. +-- @field #OPSGROUP.Beacon tacanDefault Default TACAN settings. +-- @field #boolean tacanOn If true, TACAN is currently active. -- --- @field #string roeDefault Default ROE setting. --- @field #string roe Current ROE setting. +-- @field #OPSGROUP.Beacon icls Current ICLS settings. +-- @field #OPSGROUP.Beacon iclsDefault Default ICLS settings. +-- @field #boolean iclsOn If true, ICLS is currently active. -- --- @field #string rotDefault Default ROT setting. --- @field #string rot Current ROT setting. --- --- @field #string alarmstateDefault Default Alarm State setting. --- @field #string alarmstate Current Alarm State setting. --- --- @field #number formationDefault Default formation setting. --- @field #number formation Current formation setting. +-- @field #OPSGROUP.Option option Current optional settings. +-- @field #OPSGROUP.Option optionDefault Default option settings. -- -- @field Core.Astar#ASTAR Astar path finding. -- @@ -129,6 +117,17 @@ OPSGROUP = { groupinitialized = nil, respawning = nil, wpcounter = 1, + radio = {}, + radioDefault = {}, + option = {}, + optionDefault = {}, + tacan = {}, + tacanDefault = {}, + icls = {}, + iclsDefault = {}, + callsign = {}, + callsignDefault = {}, + } --- Status of group element. @@ -199,6 +198,33 @@ OPSGROUP.TaskType={ -- @field DCS#Task DCStask DCS task structure table. -- @field #number WaypointIndex Waypoint number at which the enroute task is added. +--- Beacon data. +-- @type OPSGROUP.Beacon +-- @field #number Channel Channel. +-- @field #number Morse Morse Code. +-- @field #string Band Band "X" or "Y" for TACAN beacon. +-- @field #string UnitName Name of the unit acting as beacon. + +--- Radio data. +-- @type OPSGROUP.Radio +-- @field #number Freq Frequency +-- @field #number Modu Modulation. + +--- Callsign data +-- @type OPSGROUP.Callsign +-- @field #number Name +-- @field #number Number1 Number 1 +-- @field #number Number2 Number 2 + +--- Option data. +-- @type OPSGROUP.Option +-- @field #number ROE Rule of engagement. +-- @field #number ROT Reaction on threat. +-- @field #number Alarm Alarm state. +-- @field #number Formation Formation. +-- @field #boolean EPLRS data link. +-- @field #boolean Disperse Disperse under fire. + --- Ammo data. -- @type OPSGROUP.Ammo -- @field #number Total Total amount of ammo. @@ -831,8 +857,6 @@ function OPSGROUP:GetExpectedSpeed() end - - --- Remove a waypoint with a ceratin UID. -- @param #OPSGROUP self -- @param #number uid Waypoint UID. @@ -2723,7 +2747,7 @@ end -- @param #number roe ROE of group. Default is `ENUMS.ROE.ReturnFire`. -- @return #OPSGROUP self function OPSGROUP:SetDefaultROE(roe) - self.roeDefault=roe or ENUMS.ROE.ReturnFire + self.optionDefault.ROE=roe or ENUMS.ROE.ReturnFire return self end @@ -2731,13 +2755,13 @@ end -- @param #OPSGROUP self -- @param #string roe ROE of group. Default is the value defined by :SetDefaultROE(). -- @return #OPSGROUP self -function OPSGROUP:SetOptionROE(roe) +function OPSGROUP:SwitchROE(roe) - self.roe=roe or self.roeDefault + self.option.ROE=roe or self.optionDefault.ROE if self:IsAlive() then - self.group:OptionROE(self.roe) + self.group:OptionROE(self.option.ROE) self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.roe)) else @@ -2751,15 +2775,15 @@ end -- @param #OPSGROUP self -- @return #number Current ROE. function OPSGROUP:GetROE() - return self.roe + return self.option.ROE end --- Set the default ROT for the group. This is the ROT state gets when the group is spawned or to which it defaults back after a mission. -- @param #OPSGROUP self --- @param #number roe ROE of group. Default is ENUMS.ROT.PassiveDefense. +-- @param #number rot ROT of group. Default is ENUMS.ROT.PassiveDefense. -- @return #OPSGROUP self -function OPSGROUP:SetDefaultROT(roe) - self.rotDefault=roe or ENUMS.ROT.PassiveDefense +function OPSGROUP:SetDefaultROT(rot) + self.optionDefault.ROT=rot or ENUMS.ROT.PassiveDefense return self end @@ -2767,13 +2791,13 @@ end -- @param #OPSGROUP self -- @param #string rot ROT of group. Default is the value defined by :SetDefaultROT(). -- @return #OPSGROUP self -function OPSGROUP:SetOptionROT(rot) +function OPSGROUP:SwitchROT(rot) - self.rot=rot or self.rotDefault + self.option.ROT=rot or self.optionDefault.ROT if self:IsAlive() then - self.group:OptionROT(self.rot) + self.group:OptionROT(self.option.ROT) self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.rot)) else @@ -2783,13 +2807,20 @@ function OPSGROUP:SetOptionROT(rot) return self end +--- Get current ROT of the group. +-- @param #OPSGROUP self +-- @return #number Current ROT. +function OPSGROUP:GetROT() + return self.option.ROT +end + --- Set the default Alarm State for the group. This is the state gets when the group is spawned or to which it defaults back after a mission. -- @param #OPSGROUP self -- @param #number alarmstate Alarm state of group. Default is `AI.Option.Ground.val.ALARM_STATE.AUTO` (0). -- @return #OPSGROUP self function OPSGROUP:SetDefaultAlarmstate(alarmstate) - self.alarmstateDefault=alarmstate or 0 + self.optionDefault.Alarm=alarmstate or 0 return self end @@ -2797,24 +2828,24 @@ end -- @param #OPSGROUP self -- @param #string alarmstate Alarm state of group. Default is the value defined by :SetDefaultAlarmstate(). -- @return #OPSGROUP self -function OPSGROUP:SetOptionAlarmstate(alarmstate) +function OPSGROUP:SwitchAlarmstate(alarmstate) - self.alarmstate=alarmstate or self.alarmstateDefault + self.option.Alarm=alarmstate or self.optionDefault.Alarm if self:IsAlive() then - if self.alarmstate==0 then + if self.option.Alarm==0 then self.group:OptionAlarmStateAuto() - elseif self.alarmstate==1 then + elseif self.option.Alarm==1 then self.group:OptionAlarmStateGreen() - elseif self.alarmstate==2 then + elseif self.option.Alarm==2 then self.group:OptionAlarmStateRed() else self:E("ERROR: Unknown Alarm State! Setting to AUTO.") self.group:OptionAlarmStateAuto() end - self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.alarmstate)) + self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) else -- TODO WARNING end @@ -2826,7 +2857,217 @@ end -- @param #OPSGROUP self -- @return #number Current Alarm State. function OPSGROUP:GetAlarmstate() - return self.alarmstate + return self.option.Alarm +end + +--- Set default TACAN parameters. AA TACANs are always on "Y" band. +-- @param #OPSGROUP self +-- @param #number Channel TACAN channel. +-- @param #string Morse Morse code. Default "XXX". +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultTACAN(Channel, Morse) + + self.tacanChannelDefault=Channel + self.tacanMorseDefault=Morse or "XXX" + + self.tacan.Channel=Channel + self.tacan.Band=Band + self.tacan.Morse=Morse or "XXX" + self.tacan.UnitName=UnitName + + return self +end + +--- Activate TACAN beacon. +-- @param #OPSGROUP self +-- @param #number TACANChannel TACAN Channel. +-- @param #string TACANMorse TACAN morse code. +-- @return #OPSGROUP self +function OPSGROUP:SwitchTACAN(TACANChannel, TACANMorse) + + if self:IsAlive() then + + local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT + + if unit and unit:IsAlive() then + + local Type=4 + local System=5 + local UnitID=unit:GetID() + local TACANMode="Y" + local Frequency=UTILS.TACANToFrequency(TACANChannel, TACANMode) + + unit:CommandActivateBeacon(Type, System, Frequency, UnitID, TACANChannel, TACANMode, true, TACANMorse, true) + + self.tacanBeacon=unit + self.tacanChannel=TACANChannel + self.tacanMorse=TACANMorse + + self.tacanOn=true + + self:I(self.lid..string.format("Switching TACAN to Channel %dY Morse %s", self.tacanChannel, tostring(self.tacanMorse))) + + end + + end + + return self +end + +--- Deactivate TACAN beacon. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:SwitchTACANOff() + + if self.tacanBeacon and self.tacanBeacon:IsAlive() then + self.tacanBeacon:CommandDeactivateBeacon() + end + + self:I(self.lid..string.format("Switching TACAN OFF")) + + self.tacanOn=false + +end + +--- Set default Radio frequency and modulation. +-- @param #OPSGROUP self +-- @param #number Frequency Radio frequency in MHz. Default 251 MHz. +-- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultRadio(Frequency, Modulation) + + self.radioDefault.Freq=Frequency or 251 + self.radioDefault.Modu=Modulation or radio.modulation.AM + + return self +end + +--- Get current Radio frequency and modulation. +-- @param #OPSGROUP self +-- @return #number Radio frequency in MHz or nil. +-- @return #number Radio modulation or nil. +function OPSGROUP:GetRadio() + return self.radio.Freq, self.radio.Modu +end + +--- Turn radio on. +-- @param #OPSGROUP self +-- @param #number Frequency Radio frequency in MHz. +-- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. +-- @return #OPSGROUP self +function OPSGROUP:SwitchRadio(Frequency, Modulation) + + if self:IsAlive() and Frequency then + + Modulation=Modulation or radio.Modulation.AM + + local group=self.group --Wrapper.Group#GROUP + + if not self.radioOn then + group:SetOption(AI.Option.Air.id.SILENCE, false) + end + + group:CommandSetFrequency(Frequency, Modulation) + + self.radio.Freq=Frequency + self.radio.Modu=Modulation + + -- Radio is on. + self.radioOn=true + + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radioFreq, UTILS.GetModulationName(self.radioModu))) + + end + + return self +end + +--- Turn radio off. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:TurnRadioOff() + + if self:IsAlive() then + + self.group:SetOption(AI.Option.Air.id.SILENCE, true) + + --self.radioFreq=nil + --self.radioModu=nil + + -- Radio is off. + self.radioOn=false + + self:I(self.lid..string.format("Switching radio OFF")) + + end + + return self +end + +--- Set default formation. +-- @param #OPSGROUP self +-- @param #number Formation The formation the groups flies in. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultFormation(Formation) + + self.formationDefault=Formation + + return self +end + +--- Switch to a specific formation. +-- @param #OPSGROUP self +-- @param #number Formation New formation the group will fly in. +-- @return #OPSGROUP self +function OPSGROUP:SwitchFormation(Formation) + + if self:IsAlive() and Formation then + + self.group:SetOption(AI.Option.Air.id.FORMATION, Formation) + + self.formation=Formation + + self:I(self.lid..string.format("Switching formation to %d", self.formation)) + + end + + return self +end + +--- Set default formation. +-- @param #OPSGROUP self +-- @param #number CallsignName Callsign name. +-- @param #number CallsignNumber Callsign number. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) + + self.callsignNameDefault=CallsignName + self.callsignNumberDefault=CallsignNumber or 1 + + return self +end + +--- Switch to a specific callsign. +-- @param #OPSGROUP self +-- @param #number CallsignName Callsign name. +-- @param #number CallsignNumber Callsign number. +-- @return #OPSGROUP self +function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) + + if self:IsAlive() and CallsignName then + + self.callsignName=CallsignName + self.callsignNumber=CallsignNumber or 1 + + self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsignName, self.callsignNumber)) + + local group=self.group --Wrapper.Group#GROUP + + group:CommandSetCallsign(self.callsignName, self.callsignNumber) + + end + + return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua new file mode 100644 index 000000000..7d53dd3b6 --- /dev/null +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -0,0 +1,295 @@ +--- **Utils** - Lua Profiler. +-- +-- +-- +-- === +-- +-- ### Author: **TAW CougarNL**, *funkyfranky* +-- +-- @module Utilities.PROFILER +-- @image MOOSE.JPG + + +--- PROFILER class. +-- @type PROFILER +-- @field #string ClassName Name of the class. +-- @field #table Counters Counters. +-- @field #table dInfo Info. +-- @field #table fTime Function time. +-- @field #table fTimeTotal Total function time. +-- @field #table eventhandler Event handler to get mission end event. + +--- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? * +-- +-- === +-- +-- ![Banner Image](..\Presentations\Utilities\PROFILER_Main.jpg) +-- +-- # The PROFILER Concept +-- +-- Profile your lua code. +-- +-- # Prerequisites +-- +-- The modules **os** and **lfs** need to be desanizied. +-- +-- +-- # Start +-- +-- The profiler can simply be started by +-- +-- PROFILER.Start() +-- +-- The start can be delayed by specifying a the amount of seconds as argument, e.g. PROFILER.Start(60) to start profiling in 60 seconds. +-- +-- # Stop +-- +-- The profiler automatically stops when the mission ends. But it can be stopped any time by calling +-- +-- PROFILER.Stop() +-- +-- The stop call can be delayed by specifying the delay in seconds as optional argument, e.g. PROFILER.Stop(120) to stop it in 120 seconds. +-- +-- # Output +-- +-- The profiler output is written to a file in your DCS home folder +-- +-- X:\User\\Saved Games\DCS OpenBeta\Logs +-- +-- ## Sort By +-- +-- By default the output is sorted with respect to the total time a function used. +-- +-- The output can also be sorted with respect to the number of times the function was called by setting +-- +-- PROFILER.sortBy=1 +-- +-- Lua profiler. +-- @field #PROFILER +PROFILER = { + ClassName = "PROFILER", + Counters = {}, + dInfo = {}, + fTime = {}, + fTimeTotal = {}, + eventHandler = {}, + startTime = nil, + endTime = nil, + runTime = nil, + sortBy = 1, + logUnknown = false, + lowCpsThres = 5, +} + +PROFILER.sortBy=1 -- Sort reports by 0=Count, 1=Total time by function +PROFILER.logUnknown=false -- Log unknown functions +PROFILER.lowCpsThres=5 -- Skip results with less than X calls per second + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start/Stop Profiler +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start profiler. +function PROFILER.Start() + + PROFILER.startTime=timer.getTime() + PROFILER.endTime=0 + PROFILER.runTime=0 + + -- Set hook. + debug.sethook(PROFILER.hook, "cr") + + -- Add event handler. + world.addEventHandler(PROFILER.eventHandler) + + -- Message to screen. + local function showProfilerRunning() + timer.scheduleFunction(showProfilerRunning, nil, timer.getTime()+600) + trigger.action.outText("### Profiler running ###", 600) + end + + -- Message. + showProfilerRunning() + +end + +--- Stop profiler. +-- @param #number delay Delay before stop in seconds. +function PROFILER.Stop(delay) + + if delay and delay>0 then + + BASE:ScheduleOnce(delay, PROFILER.Stop) + + else + + -- Remove hook. + debug.sethook() + + -- Set end time. + PROFILER.endTime=timer.getTime() + + -- Run time. + PROFILER.runTime=PROFILER.endTime-PROFILER.startTime + + -- Show info. + PROFILER.showInfo() + + end + +end + +--- Event handler. +function PROFILER.eventHandler:onEvent(event) + if event.id==world.event.S_EVENT_MISSION_END then + PROFILER.Stop() + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Hook +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Debug hook. +-- @param #table event Event. +function PROFILER.hook(event) + + local f=debug.getinfo(2, "f").func + + if event=='call' then + + if PROFILER.Counters[f]==nil then + + PROFILER.Counters[f]=1 + PROFILER.dInfo[f]=debug.getinfo(2,"Sn") + + if PROFILER.fTimeTotal[f]==nil then + PROFILER.fTimeTotal[f]=0 + end + + else + PROFILER.Counters[f]=PROFILER.Counters[f]+1 + end + + if PROFILER.fTime[f]==nil then + PROFILER.fTime[f]=os.clock() + end + + elseif (event=='return') then + + if PROFILER.fTime[f]~=nil then + PROFILER.fTimeTotal[f]=PROFILER.fTimeTotal[f]+(os.clock()-PROFILER.fTime[f]) + PROFILER.fTime[f]=nil + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Data +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get data. +-- @param #function func Function. +-- @param #boolean detailed Not used. +function PROFILER.getData(func, detailed) + local n=PROFILER.dInfo[func] + if n.what=="C" then + return n.name, "", "", PROFILER.fTimeTotal[func] + end + return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func] +end + +--- Write text to log file. +-- @param #function f The file. +-- @param #string txt The text. +function PROFILER._flog(f, txt) + f:write(txt.."\r\n") + env.info("Profiler Analysis") + env.info(txt) +end + +--- Show table. +-- @param #table t Data table. +-- @param #function f The file. +-- @param #boolean detailed Show detailed info. +function PROFILER.showTable(t, f, detailed) + for i=1, #t do + + local cps=t[i].count/PROFILER.runTime + + if (cps>=PROFILER.lowCpsThres) then + + if (detailed==false) then + PROFILER._flog(f,"- Function: "..t[i].func..": "..tostring(t[i].count).." ("..string.format("%.01f",cps).."/sec) Time: "..string.format("%g",t[i].tm).." seconds") + else + PROFILER._flog(f,"- Function: "..t[i].func..": "..tostring(t[i].count).." ("..string.format("%.01f",cps).."/sec) "..tostring(t[i].src)..":"..tostring(t[i].line).." Time: "..string.format("%g",t[i].tm).." seconds") + end + + end + end +end + +--- Write info to output file. +function PROFILER.showInfo() + + -- Output file. + local file=lfs.writedir()..[[Logs\]].."_LuaProfiler.txt" + local f=io.open(file, 'w') + + -- Gather data. + local t={} + for func, count in pairs(PROFILER.Counters) do + + local s,src,line,tm=PROFILER.getData(func, false) + + if PROFILER.logUnknown==true then + if s==nil then s="" end + end + + if (s~=nil) then + t[#t+1]= + { func=s, + src=src, + line=line, + count=count, + tm=tm, + } + end + + end + + -- Sort result. + if PROFILER.sortBy==0 then + table.sort(t, function(a,b) return a.count>b.count end ) + end + if (PROFILER.sortBy==1) then + table.sort(t, function(a,b) return a.tm>b.tm end ) + end + + -- Write data. + PROFILER._flog(f,"") + PROFILER._flog(f,"#### #### #### #### #### ##### #### #### #### #### ####") + PROFILER._flog(f,"#### #### #### ---- Profiler Report ---- #### #### ####") + PROFILER._flog(f,"#### Profiler Runtime: "..string.format("%.01f",PROFILER.runTime/60).." minutes") + PROFILER._flog(f,"#### #### #### #### #### ##### #### #### #### #### ####") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, false) + + -- Detailed data. + PROFILER._flog(f,"") + PROFILER._flog(f,"#### #### #### #### #### #### #### #### #### #### #### #### ####") + PROFILER._flog(f,"#### #### #### ---- Profiler Detailed Report ---- #### #### ####") + PROFILER._flog(f,"#### #### #### #### #### #### #### #### #### #### #### #### ####") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, true) + + -- Closing. + PROFILER._flog(f,"") + PROFILER._flog(f,"#### #### #### #### #### #### #### #### #### #### #### #### ####") + + -- Close file. + f:close() +end + From 775b9e9fde6ddc4d414b4858c53c205cb4721469 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 1 Aug 2020 00:25:12 +0200 Subject: [PATCH 23/79] Fixed bugs with new option table. --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/FlightGroup.lua | 73 +++++++++---------- Moose Development/Moose/Ops/NavyGroup.lua | 4 +- Moose Development/Moose/Ops/OpsGroup.lua | 54 +++++++++----- .../Moose/Utilities/Profiler.lua | 21 ++++-- Moose Development/Moose/Wrapper/Unit.lua | 14 +--- Moose Setup/Moose.files | 2 + 7 files changed, 93 insertions(+), 76 deletions(-) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index d13ac38b5..1aaaff592 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -1,6 +1,7 @@ __Moose.Include( 'Scripts/Moose/Utilities/Enums.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Routines.lua' ) __Moose.Include( 'Scripts/Moose/Utilities/Utils.lua' ) +__Moose.Include( 'Scripts/Moose/Utilities/Profiler.lua' ) __Moose.Include( 'Scripts/Moose/Core/Base.lua' ) __Moose.Include( 'Scripts/Moose/Core/UserFlag.lua' ) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index cd3087562..f442bd46f 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1392,8 +1392,8 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) if self.ai then -- Set default ROE and ROT options. - self:SetOptionROE(self.roe) - self:SetOptionROT(self.rot) + self:SwitchROE(self.option.ROE) + self:SwitchROT(self.option.ROT) -- TODO: make this input. self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true) @@ -1402,18 +1402,19 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) -- Turn TACAN beacon on. - if self.tacanChannelDefault then - self:SwitchTACANOn(self.tacanChannelDefault, self.tacanMorseDefault) + if self.tacanDefault.Channel then + self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse) end -- Turn on the radio. - if self.radioFreqDefault then - self:SwitchRadioOn(self.radioFreqDefault, self.radioModuDefault) + if self.radioDefault.Freq then + self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) end -- Set callsign. - if self.callsignNameDefault then + if self.callsignDefault.Name then self:SwitchCallsign(self.callsignNameDefault, self.callsignNumberDefault) + else end -- Update route. @@ -2510,36 +2511,28 @@ function FLIGHTGROUP:_InitGroup() -- Radio parameters from template. self.radioOn=self.template.communication - self.radioFreq=self.template.frequency - self.radioModu=self.template.modulation - - -- If not set by the use explicitly yet, we take the template values as defaults. - if not self.radioFreqDefault then - self.radioFreqDefault=self.radioFreq - self.radioModuDefault=self.radioModu - end + self.radio.Freq=self.template.frequency + self.radio.Modu=self.template.modulation + self.radioDefault.Freq=self.radio.Freq + self.radioDefault.Modu=self.radio.Modu -- Set default formation. - if not self.formationDefault then - if self.ishelo then - self.formationDefault=ENUMS.Formation.RotaryWing.EchelonLeft.D300 - else - self.formationDefault=ENUMS.Formation.FixedWing.EchelonLeft.Group - end + if self.ishelo then + self.optionDefault.Formation=ENUMS.Formation.RotaryWing.EchelonLeft.D300 + else + self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group end + -- Is this purely AI? self.ai=not self:_IsHuman(self.group) + -- Create Menu. if not self.ai then self.menu=self.menu or {} self.menu.atc=self.menu.atc or {} self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") end - -- Switch to default formation. - -- TODO: Should this be moved to onafterspawned? - self:SwitchFormation(self.formationDefault) - -- Add elemets. for _,unit in pairs(self.group:GetUnits()) do local element=self:AddElementByName(unit:GetName()) @@ -2573,7 +2566,7 @@ function FLIGHTGROUP:_InitGroup() text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radioOn)) text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) @@ -2965,28 +2958,34 @@ end --- Add an AIR waypoint to the flight plan. -- @param #FLIGHTGROUP self -- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. --- @param #number speed Speed in knots. Default 350 kts. --- @param #number wpnumber Waypoint number. Default at the end. --- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call. +-- @param #number Speed Speed in knots. Default 350 kts. +-- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. +-- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. -function FLIGHTGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) +function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Updateroute) - -- Waypoint number. Default is at the end. - wpnumber=wpnumber or #self.waypoints+1 + -- Set waypoint index. + local wpnumber=#self.waypoints+1 + if wpnumber then + local index=self:GetWaypointIndex(AfterWaypointWithID) + if index then + wpnumber=index+1 + end + end if wpnumber>self.currentwp then self.passedfinalwp=false end -- Speed in knots. - speed=speed or 350 + Speed=Speed or 350 -- Speed at waypoint. - local speedkmh=UTILS.KnotsToKmph(speed) + local speedkmh=UTILS.KnotsToKmph(Speed) -- Create air waypoint. local name=string.format("Added Waypoint #%d", wpnumber) - local wp=coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, name) + local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, name) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) @@ -2995,10 +2994,10 @@ function FLIGHTGROUP:AddWaypoint(coordinate, speed, wpnumber, updateroute) self:_AddWaypoint(waypoint, wpnumber) -- Debug info. - self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) + self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) -- Update route. - if updateroute==nil or updateroute==true then + if Updateroute==nil or Updateroute==true then self:__UpdateRoute(-1) end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 048aac73f..674efd470 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -550,8 +550,8 @@ function NAVYGROUP:onafterSpawned(From, Event, To) if self.ai then -- Set default ROE and Alarmstate options. - self:SetOptionROE(self.roe) - self:SetOptionAlarmstate(self.alarmstate) + self:SwitchROE(self.option.ROE) + self:SwitchAlarmstate(self.option.Alarm) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 894d8ecf8..af03bc341 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -52,8 +52,9 @@ -- @field Core.Point#COORDINATE position Position of the group at last status check. -- @field #number traveldist Distance traveled in meters. This is a lower bound! -- @field #number traveltime Time. --- @field #boolean ispathfinding If true, group is on pathfinding route. -- +-- @field Core.Astar#ASTAR Astar path finding. +-- @field #boolean ispathfinding If true, group is on pathfinding route. -- -- @field #OPSGROUP.Radio radio Current radio settings. -- @field #OPSGROUP.Radio radioDefault Default radio settings. @@ -69,9 +70,10 @@ -- @field #boolean iclsOn If true, ICLS is currently active. -- -- @field #OPSGROUP.Option option Current optional settings. --- @field #OPSGROUP.Option optionDefault Default option settings. +-- @field #OPSGROUP.Option optionDefault Default option settings. -- --- @field Core.Astar#ASTAR Astar path finding. +-- @field #OPSGROUP.Callsign callsign Current callsign settings. +-- @field #OPSGROUP.Callsign callsignDefault Default callsign settings. -- -- @extends Core.Fsm#FSM @@ -2763,7 +2765,7 @@ function OPSGROUP:SwitchROE(roe) self.group:OptionROE(self.option.ROE) - self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.roe)) + self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) else -- TODO WARNING end @@ -2799,7 +2801,7 @@ function OPSGROUP:SwitchROT(rot) self.group:OptionROT(self.option.ROT) - self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.rot)) + self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) else -- TODO WARNING end @@ -2880,10 +2882,11 @@ end --- Activate TACAN beacon. -- @param #OPSGROUP self --- @param #number TACANChannel TACAN Channel. --- @param #string TACANMorse TACAN morse code. +-- @param #number Channel TACAN Channel. +-- @param #string Morse TACAN morse code. +-- @param #string Band TACAN channel mode "X" or "Y". Default is "Y" for aircraft and "X" for ground and naval groups. -- @return #OPSGROUP self -function OPSGROUP:SwitchTACAN(TACANChannel, TACANMorse) +function OPSGROUP:SwitchTACAN(Channel, Morse, Band) if self:IsAlive() then @@ -2891,21 +2894,36 @@ function OPSGROUP:SwitchTACAN(TACANChannel, TACANMorse) if unit and unit:IsAlive() then - local Type=4 - local System=5 local UnitID=unit:GetID() - local TACANMode="Y" - local Frequency=UTILS.TACANToFrequency(TACANChannel, TACANMode) - unit:CommandActivateBeacon(Type, System, Frequency, UnitID, TACANChannel, TACANMode, true, TACANMorse, true) + local Type=BEACON.Type.TACAN + local System=BEACON.System.TACAN + + --local TACANMode="Y" + if self.isAircraft then + Type=4 + System=5 + Band=Band or "Y" + else + Band=Band or "X" + end + + -- Tacan frequency. + local Frequency=UTILS.TACANToFrequency(Channel, Band) + + unit:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Band, true, Morse, true) self.tacanBeacon=unit - self.tacanChannel=TACANChannel - self.tacanMorse=TACANMorse + + self.tacan.Channel=Channel + self.tacan.Morse=Morse + self.tacan.Band=Band + self.tacan.UnitName=unit:GetName() + -- TACAN is now on. self.tacanOn=true - self:I(self.lid..string.format("Switching TACAN to Channel %dY Morse %s", self.tacanChannel, tostring(self.tacanMorse))) + self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.UnitName)) end @@ -2963,7 +2981,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) local group=self.group --Wrapper.Group#GROUP - if not self.radioOn then + if self.isAircraft and not self.radioOn then group:SetOption(AI.Option.Air.id.SILENCE, false) end @@ -2975,7 +2993,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) -- Radio is on. self.radioOn=true - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radioFreq, UTILS.GetModulationName(self.radioModu))) + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) end diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 7d53dd3b6..1e2ff7006 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -27,7 +27,8 @@ -- -- # The PROFILER Concept -- --- Profile your lua code. +-- Profile your lua code. This tells you, which functions are called very often and which consume most CPU time. +-- With this information you could optimize the perfomance of your code. -- -- # Prerequisites -- @@ -56,7 +57,7 @@ -- -- X:\User\\Saved Games\DCS OpenBeta\Logs -- --- ## Sort By +-- ## Sort Output -- -- By default the output is sorted with respect to the total time a function used. -- @@ -64,7 +65,6 @@ -- -- PROFILER.sortBy=1 -- --- Lua profiler. -- @field #PROFILER PROFILER = { ClassName = "PROFILER", @@ -84,6 +84,7 @@ PROFILER = { PROFILER.sortBy=1 -- Sort reports by 0=Count, 1=Total time by function PROFILER.logUnknown=false -- Log unknown functions PROFILER.lowCpsThres=5 -- Skip results with less than X calls per second +PROFILER.fileName="_LuaProfiler.txt" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start/Stop Profiler @@ -95,9 +96,6 @@ function PROFILER.Start() PROFILER.startTime=timer.getTime() PROFILER.endTime=0 PROFILER.runTime=0 - - -- Set hook. - debug.sethook(PROFILER.hook, "cr") -- Add event handler. world.addEventHandler(PROFILER.eventHandler) @@ -111,6 +109,12 @@ function PROFILER.Start() -- Message. showProfilerRunning() + -- Info in log. + BASE:I('############################ Profiler Started ############################') + + -- Set hook. + debug.sethook(PROFILER.hook, "cr") + end --- Stop profiler. @@ -207,7 +211,6 @@ end function PROFILER._flog(f, txt) f:write(txt.."\r\n") env.info("Profiler Analysis") - env.info(txt) end --- Show table. @@ -235,9 +238,11 @@ end function PROFILER.showInfo() -- Output file. - local file=lfs.writedir()..[[Logs\]].."_LuaProfiler.txt" + local file=lfs.writedir()..[[Logs\]]..PROFILER.fileName local f=io.open(file, 'w') + BASE:I(string.format("### Profiler: Writing result to file ", file)) + -- Gather data. local t={} for func, count in pairs(PROFILER.Counters) do diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index a7b2316b9..fe1c86e15 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -258,8 +258,7 @@ end --- Returns if the unit is activated. -- @param #UNIT self --- @return #boolean true if Unit is activated. --- @return #nil The DCS Unit is not existing or alive. +-- @return #boolean `true` if Unit is activated. `nil` The DCS Unit is not existing or alive. function UNIT:IsActive() self:F2( self.UnitName ) @@ -279,9 +278,7 @@ end -- If the Unit is alive and active, true is returned. -- If the Unit is alive but not active, false is returned. -- @param #UNIT self --- @return #boolean true if Unit is alive and active. --- @return #boolean false if Unit is alive but not active. --- @return #nil if the Unit is not existing or is not alive. +-- @return #boolean `true` if Unit is alive and active. `false` if Unit is alive but not active. `nil` if the Unit is not existing or is not alive. function UNIT:IsAlive() self:F3( self.UnitName ) @@ -300,7 +297,6 @@ end --- Returns the Unit's callsign - the localized string. -- @param #UNIT self -- @return #string The Callsign of the Unit. --- @return #nil The DCS Unit is not existing or alive. function UNIT:GetCallsign() self:F2( self.UnitName ) @@ -640,8 +636,7 @@ end --- Returns the unit sensors. -- @param #UNIT self --- @return DCS#Unit.Sensors --- @return #nil The DCS Unit is not existing or alive. +-- @return DCS#Unit.Sensors Table of sensors. function UNIT:GetSensors() self:F2( self.UnitName ) @@ -661,7 +656,6 @@ end --- Returns if the unit has sensors of a certain type. -- @param #UNIT self -- @return #boolean returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. --- @return #nil The DCS Unit is not existing or alive. function UNIT:HasSensors( ... ) self:F2( arg ) @@ -678,7 +672,6 @@ end --- Returns if the unit is SEADable. -- @param #UNIT self -- @return #boolean returns true if the unit is SEADable. --- @return #nil The DCS Unit is not existing or alive. function UNIT:HasSEAD() self:F2() @@ -705,7 +698,6 @@ end -- @param #UNIT self -- @return #boolean Indicates if at least one of the unit's radar(s) is on. -- @return DCS#Object The object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. --- @return #nil The DCS Unit is not existing or alive. function UNIT:GetRadar() self:F2( self.UnitName ) diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 36f5f604a..f0d3c0c0a 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -1,5 +1,7 @@ Utilities/Routines.lua Utilities/Utils.lua +Utilities/Enums.lua +Utilities/Profiler.lua Core/Base.lua Core/UserFlag.lua From d1a18913f352f8441bbbad9207f2f4ac0a917f46 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 2 Aug 2020 01:31:47 +0200 Subject: [PATCH 24/79] Ops --- Moose Development/Moose/Core/Radio.lua | 93 +++++++----- Moose Development/Moose/Ops/ArmyGroup.lua | 16 +-- Moose Development/Moose/Ops/Auftrag.lua | 50 +++++-- Moose Development/Moose/Ops/FlightGroup.lua | 55 ++++--- Moose Development/Moose/Ops/NavyGroup.lua | 17 +-- Moose Development/Moose/Ops/OpsGroup.lua | 151 +++++++++++++------- 6 files changed, 232 insertions(+), 150 deletions(-) diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Core/Radio.lua index 575b45044..175bc2106 100644 --- a/Moose Development/Moose/Core/Radio.lua +++ b/Moose Development/Moose/Core/Radio.lua @@ -429,7 +429,7 @@ BEACON = { -- @field #number VOR -- @field #number DME -- @field #number VOR_DME --- @field #number TACAN +-- @field #number TACAN TACtical Air Navigation system. -- @field #number VORTAC -- @field #number RSBN -- @field #number BROADCAST_STATION @@ -440,45 +440,74 @@ BEACON = { -- @field #number ILS_NEAR_HOMER -- @field #number ILS_LOCALIZER -- @field #number ILS_GLIDESLOPE +-- @field #number PRMG_LOCALIZER +-- @field #number PRMG_GLIDESLOPE +-- @field #number ICLS Same as ICLS glideslope. +-- @field #number ICLS_LOCALIZER +-- @field #number ICLS_GLIDESLOPE -- @field #number NAUTICAL_HOMER --- @field #number ICLS BEACON.Type={ - NULL = 0, - VOR = 1, - DME = 2, - VOR_DME = 3, - TACAN = 4, - VORTAC = 5, - RSBN = 32, - BROADCAST_STATION = 1024, - HOMER = 8, - AIRPORT_HOMER = 4104, + NULL = 0, + VOR = 1, + DME = 2, + VOR_DME = 3, + TACAN = 4, + VORTAC = 5, + RSBN = 128, + BROADCAST_STATION = 1024, + HOMER = 8, + AIRPORT_HOMER = 4104, AIRPORT_HOMER_WITH_MARKER = 4136, - ILS_FAR_HOMER = 16408, - ILS_NEAR_HOMER = 16456, - ILS_LOCALIZER = 16640, - ILS_GLIDESLOPE = 16896, - NAUTICAL_HOMER = 32776, - ICLS = 131584, + ILS_FAR_HOMER = 16408, + ILS_NEAR_HOMER = 16424, + ILS_LOCALIZER = 16640, + ILS_GLIDESLOPE = 16896, + PRMG_LOCALIZER = 33024, + PRMG_GLIDESLOPE = 33280, + ICLS = 131584, --leaving this in here but it is the same as ICLS_GLIDESLOPE + ICLS_LOCALIZER = 131328, + ICLS_GLIDESLOPE = 131584, + NAUTICAL_HOMER = 65536, + } --- Beacon systems supported by DCS. https://wiki.hoggitworld.com/view/DCS_command_activateBeacon -- @type BEACON.System --- @field #number PAR_10 --- @field #number RSBN_5 --- @field #number TACAN --- @field #number TACAN_TANKER --- @field #number ILS_LOCALIZER (This is the one to be used for AA TACAN Tanker!) --- @field #number ILS_GLIDESLOPE --- @field #number BROADCAST_STATION +-- @field #number PAR_10 ? +-- @field #number RSBN_5 Russian VOR/DME system. +-- @field #number TACAN TACtical Air Navigation system on ground. +-- @field #number TACAN_TANKER_X TACtical Air Navigation system for tankers on X band. +-- @field #number TACAN_TANKER_Y TACtical Air Navigation system for tankers on Y band. +-- @field #number VOR Very High Frequency Omni-Directional Range +-- @field #number ILS_LOCALIZER ILS localizer +-- @field #number ILS_GLIDESLOPE ILS glideslope. +-- @field #number PRGM_LOCALIZER PRGM localizer. +-- @field #number PRGM_GLIDESLOPE PRGM glideslope. +-- @field #number BROADCAST_STATION Broadcast station. +-- @field #number VORTAC Radio-based navigational aid for aircraft pilots consisting of a co-located VHF omnidirectional range (VOR) beacon and a tactical air navigation system (TACAN) beacon. +-- @field #number TACAN_AA_MODE_X TACtical Air Navigation for aircraft on X band. +-- @field #number TACAN_AA_MODE_Y TACtical Air Navigation for aircraft on Y band. +-- @field #number VORDME Radio beacon that combines a VHF omnidirectional range (VOR) with a distance measuring equipment (DME). +-- @field #number ICLS_LOCALIZER Carrier landing system. +-- @field #number ICLS_GLIDESLOPE Carrier landing system. BEACON.System={ - PAR_10 = 1, - RSBN_5 = 2, - TACAN = 3, - TACAN_TANKER = 4, - ILS_LOCALIZER = 5, - ILS_GLIDESLOPE = 6, - BROADCAST_STATION = 7, + PAR_10 = 1, + RSBN_5 = 2, + TACAN = 3, + TACAN_TANKER_X = 4, + TACAN_TANKER_Y = 5, + VOR = 6, + ILS_LOCALIZER = 7, + ILS_GLIDESLOPE = 8, + PRMG_LOCALIZER = 9, + PRMG_GLIDESLOPE = 10, + BROADCAST_STATION = 11, + VORTAC = 12, + TACAN_AA_MODE_X = 13, + TACAN_AA_MODE_Y = 14, + VORDME = 15, + ICLS_LOCALIZER = 16, + ICLS_GLIDESLOPE = 17, } --- Create a new BEACON Object. This doesn't activate the beacon, though, use @{#BEACON.ActivateTACAN} etc. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index b8db518b0..dab6351f7 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -681,13 +681,7 @@ end function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) -- Set waypoint index. - local wpnumber=#self.waypoints+1 - if wpnumber then - local index=self:GetWaypointIndex(AfterWaypointWithID) - if index then - wpnumber=index+1 - end - end + local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -- Check if final waypoint is still passed. if wpnumber>self.currentwp then @@ -697,22 +691,18 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - -- Speed at waypoint. - local speedkmh=UTILS.KnotsToKmph(Speed) - -- Create a Naval waypoint. - local wp=Coordinate:WaypointGround(speedkmh, Formation) + local wp=Coordinate:WaypointGround(UTILS.KnotsToKmph(Speed), Formation) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) - + -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) -- Debug info. self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) - -- Update route. if Updateroute==nil or Updateroute==true then self:_CheckGroupDone(1) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 7c25a4a1d..2c3b83010 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -34,7 +34,7 @@ -- @field #number duration Mission duration in seconds. -- @field Wrapper.Marker#MARKER marker F10 map marker. -- @field #boolean markerOn If true, display marker on F10 map with the AUFTRAG status. --- @field #numberr markerCoaliton Coalition to which the marker is dispayed. +-- @field #number markerCoaliton Coalition to which the marker is dispayed. -- @field #table DCStask DCS task structure. -- @field #number Ntargets Number of mission targets. -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. @@ -93,13 +93,17 @@ -- -- @field #table enrouteTasks Mission enroute tasks. -- +-- @field #number missionRepeated Number of times mission was repeated. +-- @field #number missionRepeatMax Number of times mission is repeated if failed. +-- -- @field #number radioFreq Mission radio frequency in MHz. -- @field #number radioModu Mission radio modulation (0=AM and 1=FM). -- @field #number tacanChannel Mission TACAN channel. -- @field #number tacanMorse Mission TACAN morse code. -- --- @field #number missionRepeated Number of times mission was repeated. --- @field #number missionRepeatMax Number of times mission is repeated if failed. +-- @field Ops.OpsGroup#OPSGROUP.Radio radio Radio freq and modulation. +-- @field Ops.OpsGroup#OPSGROUP.Beacon tacan TACAN setting. +-- @field Ops.OpsGroup#OPSGROUP.Beacon icls ICLS setting. -- -- @field #number optionROE ROE. -- @field #number optionROT ROT. @@ -859,7 +863,7 @@ function AUFTRAG:NewBAI(Target, Altitude) -- DCS Task options: mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAltitude=Altitude or UTILS.FeetToMeters(2000) + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) -- Mission options: mission.missionTask=ENUMS.MissionTask.GROUNDATTACK @@ -887,7 +891,7 @@ function AUFTRAG:NewSEAD(Target, Altitude) -- DCS Task options: mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG --ENUMS.WeaponFlag.Cannons mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAltitude=Altitude or UTILS.FeetToMeters(2000) + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) -- Mission options: mission.missionTask=ENUMS.MissionTask.SEAD @@ -1318,7 +1322,7 @@ end --- Set weapon type used for the engagement. -- @param #AUFTRAG self --- @param #number WeaponType Weapon type. Default is ENUMS.WeaponFlag.Auto +-- @param #number WeaponType Weapon type. Default is `ENUMS.WeaponFlag.Auto`. -- @return #AUFTRAG self function AUFTRAG:SetWeaponType(WeaponType) @@ -1434,9 +1438,10 @@ end -- @param #number Modulation Radio modulation. Default 0=AM. -- @return #AUFTRAG self function AUFTRAG:SetRadio(Frequency, Modulation) - - self.radioFreq=Frequency - self.radioModu=Modulation or 0 + + self.radio={} + self.radio.Freq=Frequency + self.radio.Modu=Modulation return self end @@ -1445,11 +1450,32 @@ end -- @param #AUFTRAG self -- @param #number Channel TACAN channel. -- @param #string Morse Morse code. Default "XXX". +-- @param #string UnitName Name of the unit in the group for which acts as TACAN beacon. Default is the first unit in the group. +-- @param #string Band Tacan channel mode ("X" or "Y"). Default is "X" for ground/naval and "Y" for aircraft. -- @return #AUFTRAG self -function AUFTRAG:SetTACAN(Channel, Morse) +function AUFTRAG:SetTACAN(Channel, Morse, UnitName, Band) - self.tacanChannel=Channel - self.tacanMorse=Morse or "XXX" + self.tacan={} + self.tacan.Channel=Channel + self.tacan.Morse=Morse or "XXX" + self.tacan.UnitName=UnitName + self.tacan.Band=Band + + return self +end + +--- Set ICLS beacon channel and Morse code for this mission. +-- @param #AUFTRAG self +-- @param #number Channel ICLS channel. +-- @param #string Morse Morse code. Default "XXX". +-- @param #string UnitName Name of the unit in the group for which acts as ICLS beacon. Default is the first unit in the group. +-- @return #AUFTRAG self +function AUFTRAG:SetICLS(Channel, Morse, UnitName) + + self.icls={} + self.icls.Channel=Channel + self.icls.Morse=Morse or "XXX" + self.icls.UnitName=UnitName return self end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f442bd46f..51773a5c5 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -269,7 +269,6 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "ElementLanded", "*") -- An element landed. self:AddTransition("*", "ElementArrived", "*") -- An element arrived. - self:AddTransition("*", "ElementOutOfAmmo", "*") -- An element is completely out of ammo. @@ -1395,27 +1394,27 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:SwitchROE(self.option.ROE) self:SwitchROT(self.option.ROT) + -- Turn TACAN beacon on. + if self.tacanDefault then + self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse) + end + + -- Turn on the radio. + if self.radioDefault then + self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) + end + + -- Set callsign. + if self.callsignDefault then + self:SwitchCallsign(self.callsignDefault.Name, self.callsignNumberDefault) + end + -- TODO: make this input. self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true) self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) -- Does not seem to work. AI still used the after burner. self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) - - -- Turn TACAN beacon on. - if self.tacanDefault.Channel then - self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse) - end - - -- Turn on the radio. - if self.radioDefault.Freq then - self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) - end - -- Set callsign. - if self.callsignDefault.Name then - self:SwitchCallsign(self.callsignNameDefault, self.callsignNumberDefault) - else - end -- Update route. self:__UpdateRoute(-0.5) @@ -2957,21 +2956,16 @@ end --- Add an AIR waypoint to the flight plan. -- @param #FLIGHTGROUP self --- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. +-- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. -- @param #number Speed Speed in knots. Default 350 kts. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. +-- @param #number Altitude Altitude in feet. Default is y-component of Coordinate. Note that these altitudes are wrt to sea level (barometric altitude). -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. -function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Updateroute) +function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Altitude, Updateroute) -- Set waypoint index. - local wpnumber=#self.waypoints+1 - if wpnumber then - local index=self:GetWaypointIndex(AfterWaypointWithID) - if index then - wpnumber=index+1 - end - end + local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) if wpnumber>self.currentwp then self.passedfinalwp=false @@ -2980,15 +2974,16 @@ function FLIGHTGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Updater -- Speed in knots. Speed=Speed or 350 - -- Speed at waypoint. - local speedkmh=UTILS.KnotsToKmph(Speed) - -- Create air waypoint. - local name=string.format("Added Waypoint #%d", wpnumber) - local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, name) + local wp=Coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(Speed), true, nil, {}) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) + + -- Set altitude. + if Altitude then + waypoint.alt=UTILS.FeetToMeters(Altitude) + end -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 674efd470..3e4e8a6b2 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -990,19 +990,13 @@ end -- @param Core.Point#COORDINATE Coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude. -- @param #number Speed Speed in knots. Default is default cruise speed or 70% of max speed. -- @param #number AfterWaypointWithID Insert waypoint after waypoint given ID. Default is to insert as last waypoint. --- @param #number Depth Depth at waypoint in meters. +-- @param #number Depth Depth at waypoint in meters. Only for submarines. -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. -function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Updateroute) +function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Updateroute) -- Set waypoint index. - local wpnumber=#self.waypoints+1 - if wpnumber then - local index=self:GetWaypointIndex(AfterWaypointWithID) - if index then - wpnumber=index+1 - end - end + local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) -- Check if final waypoint is still passed. if wpnumber>self.currentwp then @@ -1012,11 +1006,8 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Updaterou -- Speed in knots. Speed=Speed or self:GetSpeedCruise() - -- Speed at waypoint. - local speedkmh=UTILS.KnotsToKmph(Speed) - -- Create a Naval waypoint. - local wp=Coordinate:WaypointNaval(speedkmh) + local wp=Coordinate:WaypointNaval(UTILS.KnotsToKmph(Speed), Depth) -- Create waypoint data table. local waypoint=self:_CreateWaypoint(wp) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index af03bc341..12618cf18 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -205,18 +205,21 @@ OPSGROUP.TaskType={ -- @field #number Channel Channel. -- @field #number Morse Morse Code. -- @field #string Band Band "X" or "Y" for TACAN beacon. --- @field #string UnitName Name of the unit acting as beacon. +-- @field #string BeaconName Name of the unit acting as beacon. +-- @field Wrapper.Unit#UNIT BeaconUnit Unit object acting as beacon. --- Radio data. -- @type OPSGROUP.Radio -- @field #number Freq Frequency -- @field #number Modu Modulation. ---- Callsign data +--- Callsign data. -- @type OPSGROUP.Callsign --- @field #number Name --- @field #number Number1 Number 1 --- @field #number Number2 Number 2 +-- @field #number NumberS Squadron name number. +-- @field #number NumberG Group number. +-- @field #number NumberE Element number. +-- @field #string NameGroup Name of the group, e.g. Uzi. +-- @field #string NameElement Name of group element, e.g. Uzi 11. --- Option data. -- @type OPSGROUP.Option @@ -249,6 +252,7 @@ OPSGROUP.TaskType={ -- @field #string action Waypoint action (turning point, etc.). Ground groups have the formation here. -- @field #table task Waypoint task combo. -- @field #string type Waypoint type. +-- @field #string name Waypoint description. Shown in the F10 map. -- @field #number x Waypoint x-coordinate. -- @field #number y Waypoint y-coordinate. -- @field #boolean detour If true, this waypoint is not part of the normal route. @@ -653,10 +657,12 @@ end -- @return #OPSGROUP.Waypoint Waypoint data. function OPSGROUP:GetWaypointIndex(uid) - for i,_waypoint in pairs(self.waypoints) do - local waypoint=_waypoint --#OPSGROUP.Waypoint - if waypoint.uid==uid then - return i + if uid then + for i,_waypoint in pairs(self.waypoints) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + if waypoint.uid==uid then + return i + end end end @@ -696,6 +702,21 @@ function OPSGROUP:GetWaypointIndexCurrent() return self.currentwp or 1 end +--- Get waypoint index after waypoint with given ID. So if the waypoint has index 3 it will return 4. +-- @param #OPSGROUP self +-- @param #number uid Unique ID of the waypoint. Default is new waypoint index after the last current one. +-- @return #number Index after waypoint with given ID. +function OPSGROUP:GetWaypointIndexAfterID(uid) + + local index=self:GetWaypointIndex(uid) + if index then + return index+1 + else + return #self.waypoints+1 + end + +end + --- Get waypoint. -- @param #OPSGROUP self -- @param #number indx Waypoint index. @@ -1952,7 +1973,7 @@ function OPSGROUP:RouteToMission(mission, delay) local SpeedToMission=UTILS.KmphToKnots(self.speedCruise) -- Add waypoint. - local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, false) + local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, nil, false) -- Special for Troop transport. if mission.type==AUFTRAG.Type.TROOPTRANSPORT then @@ -1988,20 +2009,24 @@ function OPSGROUP:RouteToMission(mission, delay) -- ROE if mission.optionROE then - self:SetOptionROE(mission.optionROE) + self:SwitchROE(mission.optionROE) end -- ROT if mission.optionROT then - self:SetOptionROT(mission.optionROT) + self:SwitchROT(mission.optionROT) end -- Radio - if mission.radioFreq then - self:SwitchRadioOn(mission.radioFreq, mission.radioModu) + if mission.radio then + self:SwitchRadio(mission.radio.Freq, mission.radio.Modu) end -- TACAN - if mission.tacanChannel then - self:SwitchTACANOn(mission.tacanChannel, mission.tacanMorse) + if mission.tacan then + self:SwitchTACAN(mission.tacan.Channel, mission.tacan.Morse, mission.tacan.BeaconName, mission.tacan.Band) end + -- ICLS + if mission.icls then + self:SwitchICLS(mission.icls.Channel, mission.icls.Morse, mission.icls.UnitName) + end -- Formation if mission.optionFormation then self:SwitchFormation(mission.optionFormation) @@ -2489,21 +2514,29 @@ end -- Waypoints & Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Initialize Mission Editor waypoints. +--- Enhance waypoint table. -- @param #OPSGROUP self --- @param #OPSGROUP.Waypoint waypoint DCS waypoint data table. -- @return #OPSGROUP.Waypoint Waypoint data. -function OPSGROUP:_CreateWaypoint(waypoint, formation, detour) +function OPSGROUP:_CreateWaypoint(waypoint) - waypoint.uid=self.wpcounter - waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) - waypoint.detour=detour and detour or false - if formation then - waypoint.action=formation - end + -- Set uid. + waypoint.uid=self.wpcounter + + -- Waypoint has not been passed yet. waypoint.npassed=0 - waypoint.patrol=false + + -- Coordinate. + waypoint.coordinate=COORDINATE:New(waypoint.x, waypoint.alt, waypoint.y) + -- Set waypoint name. + waypoint.name=string.format("Waypoint UID=%d", waypoint.uid) + + -- Set types. + waypoint.patrol=false + waypoint.detour=false + waypoint.astar=false + + -- Increase UID counter. self.wpcounter=self.wpcounter+1 return waypoint @@ -2613,7 +2646,7 @@ function OPSGROUP:_UpdateWaypointTasks(n) if i>=n or nwaypoints==1 then -- Debug info. - self:I(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d", i, nwaypoints, wp.uid, self.currentwp)) + self:T(self.lid..string.format("Updating waypoint task for waypoint %d/%d ID=%d. Last waypoint passed %d", i, nwaypoints, wp.uid, self.currentwp)) -- Tasks of this waypoint local taskswp={} @@ -2866,64 +2899,83 @@ end -- @param #OPSGROUP self -- @param #number Channel TACAN channel. -- @param #string Morse Morse code. Default "XXX". +-- @param #string UnitName Name of the unit acting as beacon. +-- @param #string Band TACAN mode. Default is "X" for ground and "Y" for airborne units. -- @return #OPSGROUP self -function OPSGROUP:SetDefaultTACAN(Channel, Morse) - - self.tacanChannelDefault=Channel - self.tacanMorseDefault=Morse or "XXX" +function OPSGROUP:SetDefaultTACAN(Channel, Morse, UnitName, Band) - self.tacan.Channel=Channel - self.tacan.Band=Band - self.tacan.Morse=Morse or "XXX" - self.tacan.UnitName=UnitName + self.tacanDefault={} + self.tacanDefault.Channel=Channel + self.tacanDefault.Morse=Morse or "XXX" + self.tacanDefault.BeaconName=UnitName + self.tacanDefault.Band=Band return self end ---- Activate TACAN beacon. +--- Activate/switch TACAN beacon settings. -- @param #OPSGROUP self -- @param #number Channel TACAN Channel. -- @param #string Morse TACAN morse code. +-- @param #string UnitName Name of the unit in the group which should activate the TACAN beacon. Can also be given as #number to specify the unit number. Default is the first unit of the group. -- @param #string Band TACAN channel mode "X" or "Y". Default is "Y" for aircraft and "X" for ground and naval groups. -- @return #OPSGROUP self -function OPSGROUP:SwitchTACAN(Channel, Morse, Band) +function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) if self:IsAlive() then local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT + + if UnitName then + if type(UnitName)=="number" then + unit=self.group:GetUnit(UnitName) + else + unit=UNIT:FindByName(UnitName) + end + end + + if not unit then + self:E(self.lid.."ERROR: Could not get TACAN unit. Trying first unit in the group.") + unit=self.group:GetUnit(1) + end if unit and unit:IsAlive() then local UnitID=unit:GetID() - local Type=BEACON.Type.TACAN + local Type=BEACON.Type.TACAN local System=BEACON.System.TACAN - - --local TACANMode="Y" + if self.isAircraft then - Type=4 - System=5 + System=BEACON.System.TACAN_TANKER_Y Band=Band or "Y" else + System=BEACON.System.TACAN Band=Band or "X" end -- Tacan frequency. local Frequency=UTILS.TACANToFrequency(Channel, Band) + -- Activate beacon. unit:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Band, true, Morse, true) - self.tacanBeacon=unit - + -- Update info. + self.tacan={} self.tacan.Channel=Channel self.tacan.Morse=Morse self.tacan.Band=Band - self.tacan.UnitName=unit:GetName() + self.tacan.BeaconName=unit:GetName() + self.tacan.BeaconUnit=unit -- TACAN is now on. self.tacanOn=true self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.UnitName)) + + else + + self:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive.") end @@ -2937,12 +2989,11 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchTACANOff() - if self.tacanBeacon and self.tacanBeacon:IsAlive() then - self.tacanBeacon:CommandDeactivateBeacon() + if self.tacan.BeaconUnit and self.tacan.BeaconUnit:IsAlive() then + self.tacan.BeaconUnit:CommandDeactivateBeacon() end self:I(self.lid..string.format("Switching TACAN OFF")) - self.tacanOn=false end @@ -3059,8 +3110,8 @@ end -- @return #OPSGROUP self function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) - self.callsignNameDefault=CallsignName - self.callsignNumberDefault=CallsignNumber or 1 + self.callsignDefault.Name=CallsignName + self.callsignDefault.NumberG=CallsignNumber or 1 return self end From 11ec70441ac5f8ef53915c12b00546cf1ff9ce2e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 3 Aug 2020 00:31:10 +0200 Subject: [PATCH 25/79] Ops group --- Moose Development/Moose/Ops/ArmyGroup.lua | 88 +++++++++----- Moose Development/Moose/Ops/FlightGroup.lua | 10 +- Moose Development/Moose/Ops/NavyGroup.lua | 43 ++----- Moose Development/Moose/Ops/OpsGroup.lua | 109 +++++++++++++----- Moose Development/Moose/Ops/Squadron.lua | 1 + .../Moose/Utilities/Profiler.lua | 61 ++++++---- 6 files changed, 186 insertions(+), 126 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index dab6351f7..703a0e708 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -14,6 +14,7 @@ --- ARMYGROUP class. -- @type ARMYGROUP -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. +-- @field #boolean formationPerma Formation that is used permanently and overrules waypoint formations. -- @extends Ops.OpsGroup#OPSGROUP --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge @@ -29,6 +30,7 @@ -- @field #ARMYGROUP ARMYGROUP = { ClassName = "ARMYGROUP", + formationPerma = nil, } --- Navy group element. @@ -235,8 +237,8 @@ function ARMYGROUP:onafterStatus(From, Event, To) local nMissions=self:CountRemainingMissison() -- Info text. - local text=string.format("State %s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed), hdg, self.roe, self.alarmstate, self.formation, nTaskTot, nMissions) + local text=string.format("%s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", + fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed), hdg, self.option.ROE, self.option.Alarm, self.option.Formation, nTaskTot, nMissions) self:I(self.lid..text) else @@ -397,13 +399,13 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) if Speed then wp.speed=UTILS.KnotsToMps(Speed) - elseif self.speedCruise then - wp.speed=UTILS.KmphToMps(self.speedCruise) else -- Take default waypoint speed. end - if Formation then + if self.formationPerma then + wp.action=self.formationPerma + elseif Formation then wp.action=Formation end @@ -419,8 +421,8 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Later Waypoint(s) --- - if self.speedCruise then - wp.speed=UTILS.KmphToMps(self.speedCruise) + if self.formationPerma then + wp.action=self.formationPerma else -- Take default waypoint speed. end @@ -454,8 +456,9 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- No waypoints left --- - self:I(self.lid..string.format("No waypoints left")) - + self:E(self.lid..string.format("WARNING: No waypoints left ==> Full Stop!")) + self:FullStop() + end end @@ -742,8 +745,11 @@ function ARMYGROUP:_InitGroup() -- Max speed in km/h. self.speedmax=self.group:GetSpeedMax() + -- Cruise speed in km/h + self.speedCruise=self.speedmax*0.7 + -- Group ammo. - --self.ammo=self:GetAmmoTot() + self.ammo=self:GetAmmoTot() self.traveldist=0 self.traveltime=timer.getAbsTime() @@ -752,20 +758,15 @@ function ARMYGROUP:_InitGroup() -- Radio parameters from template. self.radioOn=false -- Radio is always OFF for ground. - -- If not set by the use explicitly yet, we take the template values as defaults. - if not self.radioFreqDefault then - self.radioFreqDefault=self.radioFreq - self.radioModuDefault=self.radioModu - end + -- We set some values. + self.radioDefault.Freq=133 + self.radioDefault.Modu=radio.modulation.AM + self.radio.Freq=133 + self.radio.Modu=radio.modulation.AM - -- Set default formation. - if not self.formationDefault then - if self.ishelo then - self.formationDefault=ENUMS.Formation.RotaryWing.EchelonLeft.D300 - else - self.formationDefault=ENUMS.Formation.FixedWing.EchelonLeft.Group - end - end + -- Set default formation from first waypoint. + self.option.Formation=self:GetWaypoint(1).action + self.optionDefault.Formation=self.option.Formation -- Units of the group. local units=self.group:GetUnits() @@ -801,11 +802,11 @@ function ARMYGROUP:_InitGroup() local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("AC type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) - --text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - --text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) - --text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radioOn)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) @@ -823,17 +824,40 @@ end -- Option Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Switch to a specific formation. +-- @param #ARMYGROUP self +-- @param #number Formation New formation the group will fly in. Default is the setting of `SetDefaultFormation()`. +-- @param #boolean Permanently If true, formation always used from now on. +-- @return #ARMYGROUP self +function ARMYGROUP:SwitchFormation(Formation, Permanently) + + if self:IsAlive() then + + Formation=Formation or self.optionDefault.Formation + + if Permanently then + self.formationPerma=Formation + else + self.formationPerma=nil + end + + -- Set current formation. + self.option.Formation=Formation + + self:__UpdateRoute(-1, nil, nil, Formation) + + -- Debug info. + self:I(self.lid..string.format("Switching formation to %s (permanently=%s)", self.option.Formation, tostring(Permanently))) + + end + + return self +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Get default cruise speed. --- @param #ARMYGROUP self --- @return #number Cruise speed (>0) in knots. -function ARMYGROUP:GetSpeedCruise() - return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 51773a5c5..be20612b7 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -26,7 +26,7 @@ -- @field #number ceiling Max altitude the aircraft can fly at in meters. -- @field #number tankertype The refueling system type (0=boom, 1=probe), if the group is a tanker. -- @field #number refueltype The refueling system type (0=boom, 1=probe), if the group can refuel from a tanker. --- @field #FLIGHTGROUP.Ammo ammo Ammunition data. Number of Guns, Rockets, Bombs, Missiles. +-- @field Ops.OpsGroup#OPSGROUP.Ammo ammo Ammunition data. Number of Guns, Rockets, Bombs, Missiles. -- @field #boolean ai If true, flight is purely AI. If false, flight contains at least one human player. -- @field #boolean fuellow Fuel low switch. -- @field #number fuellowthresh Low fuel threshold in percent. @@ -1395,18 +1395,18 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:SwitchROT(self.option.ROT) -- Turn TACAN beacon on. - if self.tacanDefault then + if self.tacanDefault and self.tacanDefault.Channel then self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse) end -- Turn on the radio. - if self.radioDefault then + if self.radioDefault and self.radioDefault.Freq then self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) end -- Set callsign. - if self.callsignDefault then - self:SwitchCallsign(self.callsignDefault.Name, self.callsignNumberDefault) + if self.callsignDefault and self.callsignDefault.NameSquad then + --self:SwitchCallsign(self.callsignDefault.Name, self.callsignNumberDefault) end -- TODO: make this input. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 3e4e8a6b2..8dcd054f6 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -175,18 +175,6 @@ function NAVYGROUP:SetPatrolAdInfinitum(switch) return self end ---- Set default cruise speed. This is the speed a group will take by default if no speed is specified explicitly. --- @param #NAVYGROUP self --- @param #number Speed Speed in knots. Default 70% of max speed. --- @return #NAVYGROUP self -function NAVYGROUP:SetSpeedCruise(Speed) - - self.speedCruise=Speed and UTILS.KnotsToKmph(Speed) or self.speedmax*0.7 - - return self -end - - --- Add a *scheduled* task. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. @@ -1065,7 +1053,7 @@ function NAVYGROUP:_InitGroup() self.speedCruise=self.speedmax*0.7 -- Group ammo. - --self.ammo=self:GetAmmoTot() + self.ammo=self:GetAmmoTot() self.traveldist=0 self.traveltime=timer.getAbsTime() @@ -1073,23 +1061,14 @@ function NAVYGROUP:_InitGroup() -- Radio parameters from template. self.radioOn=true -- Radio is always on for ships. - self.radioFreq=tonumber(self.template.units[1].frequency)/1000000 - self.radioModu=tonumber(self.template.units[1].modulation)/1000000 + self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 + self.radio.Modu=tonumber(self.template.units[1].modulation) + self.radioDefault.Freq=self.radio.Freq + self.radioDefault.Modu=self.radio.Modu - -- If not set by the use explicitly yet, we take the template values as defaults. - if not self.radioFreqDefault then - self.radioFreqDefault=self.radioFreq - self.radioModuDefault=self.radioModu - end - - -- Set default formation. - if not self.formationDefault then - if self.ishelo then - self.formationDefault=ENUMS.Formation.RotaryWing.EchelonLeft.D300 - else - self.formationDefault=ENUMS.Formation.FixedWing.EchelonLeft.Group - end - end + -- Set default formation. No really applicable for ships. + self.optionDefault.Formation="Off Road" + self.option.Formation=self.optionDefault.Formation -- Get all units of the group. local units=self.group:GetUnits() @@ -1125,11 +1104,11 @@ function NAVYGROUP:_InitGroup() local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("AC type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) - --text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn)) - --text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radioOn)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 12618cf18..b262cf3ce 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -215,10 +215,10 @@ OPSGROUP.TaskType={ --- Callsign data. -- @type OPSGROUP.Callsign --- @field #number NumberS Squadron name number. --- @field #number NumberG Group number. --- @field #number NumberE Element number. --- @field #string NameGroup Name of the group, e.g. Uzi. +-- @field #number NumberSquad Squadron number corresponding to a name like "Uzi". +-- @field #number NumberGroup Group number. First number after name, e.g. "Uzi-**1**-1". +-- @field #number NumberElement Element number.Second number after name, e.g. "Uzi-1-**1**" +-- @field #string NameSquad Name of the squad, e.g. "Uzi". -- @field #string NameElement Name of group element, e.g. Uzi 11. --- Option data. @@ -401,6 +401,24 @@ function OPSGROUP:GetLifePoints() end end +--- Set default cruise speed.. +-- @param #OPSGROUP self +-- @param #number Speed Speed in knots. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultSpeed(Speed) + if Speed then + self.speedCruise=UTILS.KnotsToKmph(Speed) + end + return self +end + +--- Get default cruise speed. +-- @param #OPSGROUP self +-- @return #number Cruise speed (>0) in knots. +function OPSGROUP:GetSpeedCruise() + return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) +end + --- Set detection on or off. -- @param #OPSGROUP self -- @param #boolean Switch If true, detection is on. If false or nil, detection is off. Default is off. @@ -2922,7 +2940,7 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) - if self:IsAlive() then + if self:IsAlive() and Channel then local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT @@ -2971,7 +2989,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) -- TACAN is now on. self.tacanOn=true - self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.UnitName)) + self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) else @@ -2987,7 +3005,7 @@ end --- Deactivate TACAN beacon. -- @param #OPSGROUP self -- @return #OPSGROUP self -function OPSGROUP:SwitchTACANOff() +function OPSGROUP:TurnOffTACAN() if self.tacan.BeaconUnit and self.tacan.BeaconUnit:IsAlive() then self.tacan.BeaconUnit:CommandDeactivateBeacon() @@ -3028,7 +3046,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) if self:IsAlive() and Frequency then - Modulation=Modulation or radio.Modulation.AM + Modulation=Modulation or (self.radioDefault.Modu or radio.Modulation.AM) local group=self.group --Wrapper.Group#GROUP @@ -3054,19 +3072,25 @@ end --- Turn radio off. -- @param #OPSGROUP self -- @return #OPSGROUP self -function OPSGROUP:TurnRadioOff() +function OPSGROUP:TurnOffRadio() if self:IsAlive() then - self.group:SetOption(AI.Option.Air.id.SILENCE, true) - - --self.radioFreq=nil - --self.radioModu=nil + if self.isAircraft then - -- Radio is off. - self.radioOn=false - - self:I(self.lid..string.format("Switching radio OFF")) + -- Set group to be silient. + self.group:SetOption(AI.Option.Air.id.SILENCE, true) + + --self.radio.Freq=nil + --self.radio.Modu=nil + + -- Radio is off. + self.radioOn=false + + self:I(self.lid..string.format("Switching radio OFF")) + else + self:E(self.lid.."ERROR radio can only be turned off for aircraft!") + end end @@ -3078,24 +3102,39 @@ end -- @param #number Formation The formation the groups flies in. -- @return #OPSGROUP self function OPSGROUP:SetDefaultFormation(Formation) - - self.formationDefault=Formation + + self.optionDefault.Formation=Formation return self end --- Switch to a specific formation. -- @param #OPSGROUP self --- @param #number Formation New formation the group will fly in. +-- @param #number Formation New formation the group will fly in. Default is the setting of `SetDefaultFormation()`. -- @return #OPSGROUP self function OPSGROUP:SwitchFormation(Formation) - if self:IsAlive() and Formation then - - self.group:SetOption(AI.Option.Air.id.FORMATION, Formation) + if self:IsAlive() then + + Formation=Formation or self.optionDefault.Formation - self.formation=Formation + if self.isAircraft then + self.group:SetOption(AI.Option.Air.id.FORMATION, Formation) + + elseif self.isGround then + + -- TODO: here we need to update the route. + + else + self:E(self.lid.."ERROR: Formation can only be set for aircraft or ground units!") + return self + end + + -- Set current formation. + self.option.Formation=Formation + + -- Debug info. self:I(self.lid..string.format("Switching formation to %d", self.formation)) end @@ -3110,8 +3149,8 @@ end -- @return #OPSGROUP self function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) - self.callsignDefault.Name=CallsignName - self.callsignDefault.NumberG=CallsignNumber or 1 + self.callsignDefault.NumberSquad=CallsignName + self.callsignDefault.NumberGroup=CallsignNumber or 1 return self end @@ -3123,16 +3162,16 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) - if self:IsAlive() and CallsignName then + if self:IsAlive() then - self.callsignName=CallsignName - self.callsignNumber=CallsignNumber or 1 + self.callsign.NumberSquad=CallsignName or self.callsignDefault.NumberSquad + self.callsign.NumberGroup=CallsignNumber or self.callsignDefault.NumberGroup - self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsignName, self.callsignNumber)) + self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsign.NumberSquad, self.callsign.NumberGroup)) local group=self.group --Wrapper.Group#GROUP - group:CommandSetCallsign(self.callsignName, self.callsignNumber) + group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) end @@ -3473,11 +3512,14 @@ function OPSGROUP:GetAmmoTot() Ammo.Guns=0 Ammo.Rockets=0 Ammo.Bombs=0 + Ammo.Torpedos=0 Ammo.Missiles=0 Ammo.MissilesAA=0 Ammo.MissilesAG=0 Ammo.MissilesAS=0 - + Ammo.MissilesCR=0 + Ammo.MissilesSA=0 + for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT @@ -3491,10 +3533,13 @@ function OPSGROUP:GetAmmoTot() Ammo.Guns=Ammo.Guns+ammo.Guns Ammo.Rockets=Ammo.Rockets+ammo.Rockets Ammo.Bombs=Ammo.Bombs+ammo.Bombs + Ammo.Torpedos=Ammo.Torpedos+ammo.Torpedos Ammo.Missiles=Ammo.Missiles+ammo.Missiles Ammo.MissilesAA=Ammo.MissilesAA+ammo.MissilesAA Ammo.MissilesAG=Ammo.MissilesAG+ammo.MissilesAG Ammo.MissilesAS=Ammo.MissilesAS+ammo.MissilesAS + Ammo.MissilesCR=Ammo.MissilesCR+ammo.MissilesCR + Ammo.MissilesSA=Ammo.MissilesSA+ammo.MissilesSA end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 96b4d8655..499863a46 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -422,6 +422,7 @@ function SQUADRON:GetCallsign(Asset) for i=1,Asset.nunits do local callsign={} + callsign[1]=self.callsignName callsign[2]=math.floor(self.callsigncounter / 10) callsign[3]=self.callsigncounter % 10 diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 1e2ff7006..c61a079da 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -91,39 +91,51 @@ PROFILER.fileName="_LuaProfiler.txt" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start profiler. -function PROFILER.Start() +-- @param #number Delay Delay in seconds before profiler is stated. Default is immediately. +-- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends. +function PROFILER.Start(Delay, Duration) - PROFILER.startTime=timer.getTime() - PROFILER.endTime=0 - PROFILER.runTime=0 + if Delay and Delay>0 then + BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) + else + + PROFILER.startTime=timer.getTime() + PROFILER.endTime=0 + PROFILER.runTime=0 + + -- Add event handler. + world.addEventHandler(PROFILER.eventHandler) + + -- Message to screen. + local function showProfilerRunning() + timer.scheduleFunction(showProfilerRunning, nil, timer.getTime()+600) + trigger.action.outText("### Profiler running ###", 600) + end + + -- Message. + showProfilerRunning() + + -- Info in log. + BASE:I('############################ Profiler Started ############################') - -- Add event handler. - world.addEventHandler(PROFILER.eventHandler) - - -- Message to screen. - local function showProfilerRunning() - timer.scheduleFunction(showProfilerRunning, nil, timer.getTime()+600) - trigger.action.outText("### Profiler running ###", 600) + -- Set hook. + debug.sethook(PROFILER.hook, "cr") + + if Duration then + PROFILER.Stop(Duration) + end + end - -- Message. - showProfilerRunning() - - -- Info in log. - BASE:I('############################ Profiler Started ############################') - - -- Set hook. - debug.sethook(PROFILER.hook, "cr") - end --- Stop profiler. --- @param #number delay Delay before stop in seconds. -function PROFILER.Stop(delay) +-- @param #number Delay Delay before stop in seconds. +function PROFILER.Stop(Delay) - if delay and delay>0 then + if Delay and Delay>0 then - BASE:ScheduleOnce(delay, PROFILER.Stop) + BASE:ScheduleOnce(Delay, PROFILER.Stop) else @@ -210,7 +222,6 @@ end -- @param #string txt The text. function PROFILER._flog(f, txt) f:write(txt.."\r\n") - env.info("Profiler Analysis") end --- Show table. From dafbd3f368455648a74a6aae042ccb66feb0de4f Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 4 Aug 2020 01:10:53 +0200 Subject: [PATCH 26/79] Ops & Auftrag --- Moose Development/Moose/Core/Astar.lua | 2 +- Moose Development/Moose/Ops/ArmyGroup.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 70 +++++++++++------ Moose Development/Moose/Ops/FlightGroup.lua | 44 +++++++++-- Moose Development/Moose/Ops/NavyGroup.lua | 36 ++++++--- Moose Development/Moose/Ops/OpsGroup.lua | 87 +++++++++++++++++++-- 6 files changed, 194 insertions(+), 46 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 59a199e62..984f67df0 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -475,7 +475,7 @@ end -- @return #boolean If true, two nodes have LoS. function ASTAR.LoS(nodeA, nodeB, corridor) - local offset=0.1 + local offset=1 local dx=corridor and corridor/2 or nil local dy=dx diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 703a0e708..bbea2df42 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -761,6 +761,7 @@ function ARMYGROUP:_InitGroup() -- We set some values. self.radioDefault.Freq=133 self.radioDefault.Modu=radio.modulation.AM + self.radio.Freq=133 self.radio.Modu=radio.modulation.AM diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 2c3b83010..b707b1a85 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -37,6 +37,8 @@ -- @field #number markerCoaliton Coalition to which the marker is dispayed. -- @field #table DCStask DCS task structure. -- @field #number Ntargets Number of mission targets. +-- @field #number Ncasualties Number of own casualties during mission. +-- @field #number Nelements Number of elements (units) assigned to mission. -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. -- @field #number Tover Mission abs. time stamp, when mission was over. -- @field #table conditionStart Condition(s) that have to be true, before the mission will be started. @@ -486,35 +488,38 @@ function AUFTRAG:New(Type) self.missionRepeatMax=0 self.nassets=1 self.dTevaluate=0 + self.Ncasualties=0 + self.Nelements=0 -- FMS start state is PLANNED. self:SetStartState(self.status) -- PLANNED --> (QUEUED) --> (REQUESTED) --> SCHEDULED --> STARTED --> EXECUTING --> DONE - self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. - self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. - self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. + self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. + self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. + self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. - self:AddTransition(AUFTRAG.Status.PLANNED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- From planned directly to scheduled. + self:AddTransition(AUFTRAG.Status.PLANNED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- From planned directly to scheduled. - self:AddTransition(AUFTRAG.Status.SCHEDULED, "Started", AUFTRAG.Status.STARTED) -- First asset has started the mission - self:AddTransition(AUFTRAG.Status.STARTED, "Executing", AUFTRAG.Status.EXECUTING) -- First asset is executing the mission. + self:AddTransition(AUFTRAG.Status.SCHEDULED, "Started", AUFTRAG.Status.STARTED) -- First asset has started the mission + self:AddTransition(AUFTRAG.Status.STARTED, "Executing", AUFTRAG.Status.EXECUTING) -- First asset is executing the mission. - self:AddTransition("*", "Done", AUFTRAG.Status.DONE) -- All assets have reported that mission is done. + self:AddTransition("*", "Done", AUFTRAG.Status.DONE) -- All assets have reported that mission is done. - self:AddTransition("*", "Cancel", "*") -- Command to cancel the mission. + self:AddTransition("*", "Cancel", "*") -- Command to cancel the mission. - self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS) - self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED) + self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS) + self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED) - self:AddTransition("*", "Status", "*") - self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "*") - self:AddTransition("*", "Repeat", AUFTRAG.Status.PLANNED) + self:AddTransition("*", "Repeat", AUFTRAG.Status.PLANNED) - self:AddTransition("*", "GroupDead", "*") - self:AddTransition("*", "AssetDead", "*") + self:AddTransition("*", "ElementDestroyed", "*") + self:AddTransition("*", "GroupDead", "*") + self:AddTransition("*", "AssetDead", "*") -- Init status update. self:__Status(-1) @@ -1836,7 +1841,7 @@ function AUFTRAG:onafterStatus(From, Event, To) local commander=self.wingcommander and tostring(self.wingcommander.coalition) or "N/A" -- Info message. - self:T(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) + self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) -- Check for error. if fsmstate~=self.status then @@ -1902,6 +1907,11 @@ function AUFTRAG:Evaluate() failed=true end + -- No targets and everybody died ==> mission failed. Well, unless success condition is true. + if self.Ntargets==0 and self.Nelements==self.Ncasualties then + failed=true + end + end --TODO: all assets dead? Is this a FAILED criterion even if all targets have been destroyed? What if there are no initial targets (e.g. when ORBIT, PATROL, RECON missions). @@ -1910,15 +1920,18 @@ function AUFTRAG:Evaluate() failed=true elseif successCondition then failed=false - end + end -- Debug text. local text=string.format("Evaluating mission:\n") - text=text..string.format("Targets = %d/%d\n", self.Ntargets, Ntargets) - text=text..string.format("Damage = %.1f %%\n", targetdamage) - text=text..string.format("Success Cond = %s\n", tostring(successCondition)) - text=text..string.format("Failure Cond = %s\n", tostring(failureCondition)) - text=text..string.format("Failed = %s", tostring(failed)) + text=text..string.format("Units assigned = %d\n", self.Nelements) + text=text..string.format("Own casualties = %d\n", self.Ncasualties) + text=text..string.format("Own losses = %.1f %%\n", self.Ncasualties/self.Nelements*100) + text=text..string.format("Targets = %d/%d\n", self.Ntargets, Ntargets) + text=text..string.format("Damage = %.1f %%\n", targetdamage) + text=text..string.format("Success Cond = %s\n", tostring(successCondition)) + text=text..string.format("Failure Cond = %s\n", tostring(failureCondition)) + text=text..string.format("Failed = %s", tostring(failed)) self:I(self.lid..text) if failed then @@ -2224,6 +2237,15 @@ function AUFTRAG:onafterDone(From, Event, To) end +--- On after "ElementDestroyed" event. +-- @param #AUFTRAG self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group that is dead now. +function AUFTRAG:onafterElementDestroyed(From, Event, To, OpsGroup, Element) + self.Ncasualties=self.Ncasualties+1 +end --- On after "GroupDead" event. -- @param #AUFTRAG self @@ -2412,6 +2434,10 @@ function AUFTRAG:onafterRepeat(From, Event, To) -- No flight data. self.groupdata={} + -- Reset casualties and units assigned. + self.Ncasualties=0 + self.Nelements=0 + -- Call status again. self:__Status(-30) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index be20612b7..cdc676150 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1154,8 +1154,9 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s unit lost ==> dead", element.name)) - self:ElementDead(element) + self:I(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed", element.name)) + --self:ElementDead(element) + self:ElementDestroyed(element) end end @@ -1359,6 +1360,25 @@ function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Pa self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) end +--- On after "ElementDestroyed" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #FLIGHTGROUP.Element Element The flight group element. +function FLIGHTGROUP:onafterElementDestroyed(From, Event, To, Element) + self:T(self.lid..string.format("Element dead %s.", Element.name)) + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + mission:ElementDestroyed(self, Element) + + end + +end + --- On after "ElementDead" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -1395,8 +1415,8 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:SwitchROT(self.option.ROT) -- Turn TACAN beacon on. - if self.tacanDefault and self.tacanDefault.Channel then - self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse) + if self.tacanDefault then + self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse, self.tacanDefault.BeaconName, self.tacanDefault.Band) end -- Turn on the radio. @@ -1578,7 +1598,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) self:__Stop(5*60) end ---- On after "FlightDead" event. +--- On after "Dead" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -1596,7 +1616,7 @@ function FLIGHTGROUP:onafterDead(From, Event, To) self.flightcontrol=nil end - -- Cancel all mission. + -- Cancel all missions. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -2510,8 +2530,10 @@ function FLIGHTGROUP:_InitGroup() -- Radio parameters from template. self.radioOn=self.template.communication + self.radio.Freq=self.template.frequency self.radio.Modu=self.template.modulation + self.radioDefault.Freq=self.radio.Freq self.radioDefault.Modu=self.radio.Modu @@ -3221,7 +3243,15 @@ function FLIGHTGROUP:GetClosestAirbase() local coord=group:GetCoordinate() local coalition=self:GetCoalition() - return coord:GetClosestAirbase(nil, coalition) + local airbase=coord:GetClosestAirbase() --(nil, coalition) + + if airbase then + env.info("FF Got closest airbase ".. airbase:GetName()) + else + env.info("FF no closest airbase!") + end + + return airbase end --- Search unoccupied parking spots at the airbase for all flight elements. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 8dcd054f6..df904b6eb 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -219,14 +219,11 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) -- Absolute mission time in seconds. local Tnow=timer.getAbsTime() + -- Convert number to Clock. if starttime and type(starttime)=="number" then starttime=UTILS.SecondsToClock(Tnow+starttime) end - if stoptime and type(stoptime)=="number" then - stoptime=UTILS.SecondsToClock(Tnow+stoptime) - end - -- Input or now. starttime=starttime or UTILS.SecondsToClock(Tnow) @@ -234,7 +231,16 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) local Tstart=UTILS.ClockToSeconds(starttime) -- Set stop time. - local Tstop=stoptime and UTILS.ClockToSeconds(stoptime) or Tstart+90*60 + local Tstop=Tstart+90*60 + + if stoptime==nil then + Tstop=Tstart+90*60 + elseif type(stoptime)=="number" then + Tstop=Tstart+stoptime + else + Tstop=UTILS.ClockToSeconds(stoptime) + end + -- Consistancy check for timing. if Tstart>Tstop then @@ -537,10 +543,16 @@ function NAVYGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE and Alarmstate options. - self:SwitchROE(self.option.ROE) + -- Set default ROE. + self:SwitchROE(self.option.ROE) + + -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) + if self.tacanDefault then + + end + end -- Get orientation. @@ -1063,6 +1075,7 @@ function NAVYGROUP:_InitGroup() self.radioOn=true -- Radio is always on for ships. self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 self.radio.Modu=tonumber(self.template.units[1].modulation) + self.radioDefault.Freq=self.radio.Freq self.radioDefault.Modu=self.radio.Modu @@ -1145,15 +1158,20 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) return distance end + local offsetY=1 + -- Current coordinate. - local coordinate=self:GetCoordinate():SetAltitude(0, true) + local coordinate=self:GetCoordinate():SetAltitude(offsetY, true) -- Current heading. local heading=self:GetHeading() + -- Check from 500 meters in front. + coordinate=coordinate:Translate(500, heading, true) + local function LoS(dist) local checkcoord=coordinate:Translate(dist, heading, true) - return coordinate:IsLOS(checkcoord, 0.001) + return coordinate:IsLOS(checkcoord, offsetY) end -- First check if everything is clear. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b262cf3ce..ef08d4837 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -124,12 +124,9 @@ OPSGROUP = { option = {}, optionDefault = {}, tacan = {}, - tacanDefault = {}, icls = {}, - iclsDefault = {}, callsign = {}, callsignDefault = {}, - } --- Status of group element. @@ -259,6 +256,7 @@ OPSGROUP.TaskType={ -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. +-- @field Wrapper.Marker#MARKER marker Marker on the F10 map. --- NavyGroup version. -- @field #string version @@ -352,6 +350,7 @@ function OPSGROUP:New(Group) self:AddTransition("*", "MissionDone", "*") -- Mission is over. self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. + self:AddTransition("*", "ElementDestroyed", "*") -- An element was destroyed. self:AddTransition("*", "ElementDead", "*") -- An element is dead. ------------------------ @@ -637,6 +636,67 @@ end -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get the waypoints. +-- @param #OPSGROUP self +-- @return #table Table of all waypoints. +function OPSGROUP:GetWaypoints() + return self.waypoints +end + +--- Mark waypoints on F10 map. +-- @param #OPSGROUP self +-- @param #number Duration Duration in seconds how long the waypoints are displayed before they are automatically removed. Default is that they are never removed. +-- @return #OPSGROUP self +function OPSGROUP:MarkWaypoints(Duration) + + for i,_waypoint in pairs(self.waypoints or {}) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + + local text=string.format("Waypoint ID=%d of %s", waypoint.uid, self.groupname) + text=text..string.format("\nSpeed=%.1f kts, Alt=%d ft (%s)", UTILS.MpsToKnots(waypoint.speed), UTILS.MetersToFeet(waypoint.alt), "BARO") + + if waypoint.marker then + if waypoint.marker.text~=text then + waypoint.marker.text=text + end + + else + waypoint.marker=MARKER:New(waypoint.coordinate, text):ToCoalition(self:GetCoalition()) + end + end + + + if Duration then + self:RemoveWaypointMarkers(Duration) + end + + return self +end + +--- Remove waypoints markers on the F10 map. +-- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before the markers are removed. Default is immediately. +-- @return #OPSGROUP self +function OPSGROUP:RemoveWaypointMarkers(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.RemoveWaypointMarkers, self) + else + + for i,_waypoint in pairs(self.waypoints or {}) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + + if waypoint.marker then + waypoint.marker:Remove() + end + end + + end + + return self +end + + --- Get the waypoint from its unique ID. -- @param #OPSGROUP self -- @param #number uid Waypoint unique ID. @@ -923,6 +983,12 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- Number of waypoints before delete. local N=#self.waypoints + + -- Remove waypoint marker. + local wp=self:GetWaypoint(wpindex) + if wp and wp.marker then + wp.marker:Remove() + end -- Remove waypoint. table.remove(self.waypoints, wpindex) @@ -1494,7 +1560,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) if Task.dcstask.id=="Formation" then Task.formation:Stop() self:TaskDone(Task) - elseif stopflag==1 then + elseif stopflag==1 or not self:IsAlive() then -- Manual call TaskDone if setting flag to one was not successful. self:TaskDone(Task) end @@ -1595,6 +1661,9 @@ function OPSGROUP:AddMission(Mission) -- Set mission status to SCHEDULED. Mission:Scheduled() + + -- Add elements. + Mission.Nelements=Mission.Nelements+#self.elements -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -2934,7 +3003,7 @@ end --- Activate/switch TACAN beacon settings. -- @param #OPSGROUP self -- @param #number Channel TACAN Channel. --- @param #string Morse TACAN morse code. +-- @param #string Morse TACAN morse code. Default is the value set in @{#OPSGROUP.SetDefaultTACAN} or if not set "XXX". -- @param #string UnitName Name of the unit in the group which should activate the TACAN beacon. Can also be given as #number to specify the unit number. Default is the first unit of the group. -- @param #string Band TACAN channel mode "X" or "Y". Default is "Y" for aircraft and "X" for ground and naval groups. -- @return #OPSGROUP self @@ -2956,6 +3025,10 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) self:E(self.lid.."ERROR: Could not get TACAN unit. Trying first unit in the group.") unit=self.group:GetUnit(1) end + + if not Morse then + Morse=self.tacanDefault and self.tacanDefault.Morse or "XXX" + end if unit and unit:IsAlive() then @@ -3046,7 +3119,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) if self:IsAlive() and Frequency then - Modulation=Modulation or (self.radioDefault.Modu or radio.Modulation.AM) + Modulation=Modulation or self.radioDefault.Modu local group=self.group --Wrapper.Group#GROUP @@ -3432,7 +3505,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:Dead() + self:__Dead(-1) end end From 76e75505e50b7dd759f7e8154871724c66f8ce64 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 4 Aug 2020 16:56:45 +0200 Subject: [PATCH 27/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 9 +- Moose Development/Moose/Ops/Auftrag.lua | 22 ++-- Moose Development/Moose/Ops/FlightGroup.lua | 20 ++-- Moose Development/Moose/Ops/NavyGroup.lua | 53 +++++++--- Moose Development/Moose/Ops/OpsGroup.lua | 110 +++++++++++++++++++- 5 files changed, 169 insertions(+), 45 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index bbea2df42..e22ba147e 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -306,7 +306,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:I(self.lid..text) end - self:__Status(-10) + self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -757,13 +757,8 @@ function ARMYGROUP:_InitGroup() -- Radio parameters from template. self.radioOn=false -- Radio is always OFF for ground. - - -- We set some values. - self.radioDefault.Freq=133 - self.radioDefault.Modu=radio.modulation.AM - self.radio.Freq=133 - self.radio.Modu=radio.modulation.AM + self.radio.Modu=radio.modulation.AM -- Set default formation from first waypoint. self.option.Formation=self:GetWaypoint(1).action diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index b707b1a85..b5eb856bc 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -262,7 +262,7 @@ AUFTRAG = { Debug = false, lid = nil, auftragsnummer = nil, - groupdata = {}, + groupdata = {}, assets = {}, missionFraction = 0.5, enrouteTasks = {}, @@ -432,24 +432,24 @@ AUFTRAG.version="0.3.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Clone mission. How? Deepcopy? --- DONE: Option to assign mission to specific squadrons (requires an AIRWING). -- TODO: Option to assign a specific payload for the mission (requires an AIRWING). --- DONE: Add mission start conditions. --- TODO: Add recovery tanker mission for boat ops. --- DONE: Add rescue helo mission for boat ops. -- TODO: Mission success options damaged, destroyed. +-- TODO: Recon mission. What input? Set of coordinates? +-- NOPE: Clone mission. How? Deepcopy? ==> Create a new auftrag. +-- TODO: F10 marker to create new missions. +-- TODO: Add recovery tanker mission for boat ops. +-- DONE: Option to assign mission to specific squadrons (requires an AIRWING). +-- DONE: Add mission start conditions. +-- DONE: Add rescue helo mission for boat ops. -- DONE: Mission ROE and ROT. -- DONE: Mission frequency and TACAN. --- TODO: Mission formation, etc. +-- DONE: Mission formation, etc. -- DONE: FSM events. -- DONE: F10 marker functions that are updated on Status event. --- TODO: F10 marker to create new missions. -- DONE: Evaluate mission result ==> SUCCESS/FAILURE -- DONE: NewAUTO() NewA2G NewA2A -- DONE: Transport mission. --- TODO: Recon mission. What input? Set of coordinates? --- TODO: Set mission coalition, e.g. for F10 markers. Could be derived from target if target has a coalition. +-- DONE: Set mission coalition, e.g. for F10 markers. Could be derived from target if target has a coalition. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -545,7 +545,7 @@ function AUFTRAG:NewANTISHIP(Target, Altitude) -- DCS task parameters: mission.engageWeaponType=ENUMS.WeaponFlag.Auto mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAltitude=Altitude or UTILS.FeetToMeters(2000) + mission.engageAltitude=UTILS.FeetToMeters(Altitude or 2000) -- Mission options: mission.missionTask=ENUMS.MissionTask.ANTISHIPSTRIKE diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index cdc676150..423fdd7a3 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1420,13 +1420,17 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) end -- Turn on the radio. - if self.radioDefault and self.radioDefault.Freq then + if self.radioDefault then self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) + else + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu) end -- Set callsign. - if self.callsignDefault and self.callsignDefault.NameSquad then - --self:SwitchCallsign(self.callsignDefault.Name, self.callsignNumberDefault) + if self.callsignDefault then + self:SwitchCallsign(self.callsignDefault.NumberSquad, self.callsignDefault.NumberGroup) + else + self:SetDefaultCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) end -- TODO: make this input. @@ -2529,13 +2533,15 @@ function FLIGHTGROUP:_InitGroup() self.position=self:GetCoordinate() -- Radio parameters from template. - self.radioOn=self.template.communication - + self.radioOn=self.template.communication self.radio.Freq=self.template.frequency self.radio.Modu=self.template.modulation - self.radioDefault.Freq=self.radio.Freq - self.radioDefault.Modu=self.radio.Modu + --TODO callsign from template or getCallsign + self.callsign.NumberSquad=self.template.units[1].callsign[1] + self.callsign.NumberGroup=self.template.units[1].callsign[2] + self.callsign.NumberElement=self.template.units[1].callsign[3] -- First element only + self.callsign.NameSquad=UTILS.GetCallsignName(self.callsign.NumberSquad) -- Set default formation. if self.ishelo then diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index df904b6eb..ab1af2f59 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -21,6 +21,8 @@ -- @field #table Qintowind Queue of "into wind" turns. -- @field #number depth Ordered depth in meters. -- @field #boolean collisionwarning If true, collition warning. +-- @field #boolean pathfindingOn If true, enable pathfining. +-- @field #boolean ispathfinding If true, group is currently path finding. -- @extends Ops.OpsGroup#OPSGROUP --- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson @@ -69,7 +71,8 @@ NAVYGROUP.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - + +-- TODO: Collision warning. -- DONE: Detour, add temporary waypoint and resume route. -- DONE: Stop and resume route. -- DONE: Add waypoints. @@ -112,6 +115,7 @@ function NAVYGROUP:New(GroupName) self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. self:AddTransition("*", "CollitionWarning", "*") -- Collision warning. + self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive. self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface. @@ -175,6 +179,14 @@ function NAVYGROUP:SetPatrolAdInfinitum(switch) return self end +--- Enable/disable pathfinding. +-- @param #NAVYGROUP self +-- @param #boolean switch If true, enable pathfinding. +-- @return #NAVYGROUP self +function NAVYGROUP:SetPathfinding(Switch) + self.pathfindingOn=Switch +end + --- Add a *scheduled* task. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. @@ -386,11 +398,19 @@ function NAVYGROUP:onafterStatus(From, Event, To) local freepath=10000 local collision=false + -- Only check if not currently turning. if not self:IsTurning() then + -- Check free path ahead. + freepath=self:_CheckFreePath(freepath, 100) + + if freepath<5000 then + self:CollisionWarning() + end + if not self.ispathfinding then - freepath=self:_CheckFreePath(freepath, 100) + if freepath<5000 then self.ispathfinding=self:_FindPathToNextWaypoint() @@ -498,8 +518,8 @@ function NAVYGROUP:onafterStatus(From, Event, To) end - -- Next status update in 10 seconds. - self:__Status(-10) + -- Next status update in 30 seconds. + self:__Status(-30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -549,8 +569,21 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) + -- Turn TACAN beacon on. if self.tacanDefault then + self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse, self.tacanDefault.BeaconName, self.tacanDefault.Band) + end + -- Turn ICLS on. + if self.iclsDefault then + self:SwitchICLS(self.iclsDefault.Channel, self.iclsDefault.Morse, self.iclsDefault.BeaconName) + end + + -- Turn on the radio. + if self.radioDefault then + self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) + else + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu) end end @@ -1076,9 +1109,6 @@ function NAVYGROUP:_InitGroup() self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 self.radio.Modu=tonumber(self.template.units[1].modulation) - self.radioDefault.Freq=self.radio.Freq - self.radioDefault.Modu=self.radio.Modu - -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" self.option.Formation=self.optionDefault.Formation @@ -1408,15 +1438,6 @@ function NAVYGROUP:_CheckTurnsIntoWind() end ---- Get default cruise speed. --- @param #NAVYGROUP self --- @return #number Cruise speed (>0) in knots. -function NAVYGROUP:GetSpeedCruise() - return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) -end - - - --- Check queued turns into wind. -- @param #NAVYGROUP self -- @return #NAVYGROUP.IntoWind Next into wind data. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ef08d4837..08b99b8e9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2982,7 +2982,7 @@ function OPSGROUP:GetAlarmstate() return self.option.Alarm end ---- Set default TACAN parameters. AA TACANs are always on "Y" band. +--- Set default TACAN parameters. -- @param #OPSGROUP self -- @param #number Channel TACAN channel. -- @param #string Morse Morse code. Default "XXX". @@ -2995,7 +2995,7 @@ function OPSGROUP:SetDefaultTACAN(Channel, Morse, UnitName, Band) self.tacanDefault.Channel=Channel self.tacanDefault.Morse=Morse or "XXX" self.tacanDefault.BeaconName=UnitName - self.tacanDefault.Band=Band + self.tacanDefault.Band=Band return self end @@ -3009,7 +3009,7 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) - if self:IsAlive() and Channel then + if self:IsAlive() then local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT @@ -3026,11 +3026,15 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) unit=self.group:GetUnit(1) end + if not Channel then + Channel=self.tacanDefault and self.tacanDefault.Channel or nil + end + if not Morse then Morse=self.tacanDefault and self.tacanDefault.Morse or "XXX" end - if unit and unit:IsAlive() then + if unit and unit:IsAlive() and Channel then local UnitID=unit:GetID() @@ -3089,6 +3093,103 @@ function OPSGROUP:TurnOffTACAN() end + +--- Set default ICLS parameters. +-- @param #OPSGROUP self +-- @param #number Channel ICLS channel. +-- @param #string Morse Morse code. Default "XXX". +-- @param #string UnitName Name of the unit acting as beacon. +-- @param #string Band ICLS mode. Default is "X" for ground and "Y" for airborne units. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultICLS(Channel, Morse, UnitName) + + self.iclsDefault={} + self.iclsDefault.Channel=Channel + self.iclsDefault.Morse=Morse or "XXX" + self.iclsDefault.BeaconName=UnitName + + return self +end + +--- Activate/switch ICLS beacon settings. +-- @param #OPSGROUP self +-- @param #number Channel ICLS Channel. +-- @param #string Morse ICLS morse code. Default is the value set in @{#OPSGROUP.SetDefaultICLS} or if not set "XXX". +-- @param #string UnitName Name of the unit in the group which should activate the ICLS beacon. Can also be given as #number to specify the unit number. Default is the first unit of the group. +-- @return #OPSGROUP self +function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) + + if self:IsAlive() then + + local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT + + if UnitName then + if type(UnitName)=="number" then + unit=self.group:GetUnit(UnitName) + else + unit=UNIT:FindByName(UnitName) + end + end + + if not unit then + self:E(self.lid.."ERROR: Could not get ICLS unit. Trying first unit in the group.") + unit=self.group:GetUnit(1) + end + + if not Channel then + Channel=self.iclsDefault and self.iclsDefault.Channel or nil + end + + if not Morse then + Morse=self.iclsDefault and self.iclsDefault.Morse or "XXX" + end + + if unit and unit:IsAlive() and Channel then + + local UnitID=unit:GetID() + + -- Activate beacon. + unit:CommandActivateICLS(Channel, UnitID, Morse) + + -- Update info. + self.icls={} + self.icls.Channel=Channel + self.icls.Morse=Morse + self.icls.Band=Band + self.icls.BeaconName=unit:GetName() + self.icls.BeaconUnit=unit + + -- ICLS is now on. + self.iclsOn=true + + self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) + + else + + self:E(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") + + end + + end + + return self +end + +--- Deactivate ICLS beacon. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:TurnOffICLS() + + if self.icls.BeaconUnit and self.icls.BeaconUnit:IsAlive() then + self.icls.BeaconUnit:CommandDeactivateICLS() + end + + self:I(self.lid..string.format("Switching ICLS OFF")) + self.iclsOn=false + +end + + --- Set default Radio frequency and modulation. -- @param #OPSGROUP self -- @param #number Frequency Radio frequency in MHz. Default 251 MHz. @@ -3096,6 +3197,7 @@ end -- @return #OPSGROUP self function OPSGROUP:SetDefaultRadio(Frequency, Modulation) + self.radioDefault={} self.radioDefault.Freq=Frequency or 251 self.radioDefault.Modu=Modulation or radio.modulation.AM From e6f7493ddd2ac9dcdad315d68354f927cde8dd2f Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 5 Aug 2020 00:39:48 +0200 Subject: [PATCH 28/79] Ops Fixes --- Moose Development/Moose/Ops/Auftrag.lua | 66 ++++++++++++------- Moose Development/Moose/Ops/FlightGroup.lua | 52 +++++---------- Moose Development/Moose/Ops/NavyGroup.lua | 20 +++--- Moose Development/Moose/Ops/OpsGroup.lua | 17 ++--- Moose Development/Moose/Ops/WingCommander.lua | 21 +++--- .../Moose/Wrapper/Controllable.lua | 11 ---- 6 files changed, 87 insertions(+), 100 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index b5eb856bc..0ef2d07e1 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1890,31 +1890,46 @@ function AUFTRAG:Evaluate() -- Target damage in %. local targetdamage=self:GetTargetDamage() + + -- Own damage in %. + local owndamage=self.Ncasualties/self.Nelements*100 -- Current number of mission targets. local Ntargets=self:CountMissionTargets() - -- Number of current targets is still >0 ==> Not everything was destroyed. - if self.type==AUFTRAG.Type.TROOPTRANSPORT then - - if self.Ntargets>0 and Ntargets0 then + --- + -- Mission had targets + --- + + -- Number of current targets is still >0 ==> Not everything was destroyed. + if self.type==AUFTRAG.Type.TROOPTRANSPORT then + + if Ntargets0 then + failed=true + end + + end + else + + --- + -- Mission had NO targets + --- + + -- No targets and everybody died ==> mission failed. Well, unless success condition is true. + if self.Nelements==self.Ncasualties then + failed=true + end - if self.Ntargets>0 and Ntargets>0 then - failed=true - end - - -- No targets and everybody died ==> mission failed. Well, unless success condition is true. - if self.Ntargets==0 and self.Nelements==self.Ncasualties then - failed=true - end - end - - --TODO: all assets dead? Is this a FAILED criterion even if all targets have been destroyed? What if there are no initial targets (e.g. when ORBIT, PATROL, RECON missions). if failureCondition then failed=true @@ -1924,14 +1939,19 @@ function AUFTRAG:Evaluate() -- Debug text. local text=string.format("Evaluating mission:\n") - text=text..string.format("Units assigned = %d\n", self.Nelements) - text=text..string.format("Own casualties = %d\n", self.Ncasualties) - text=text..string.format("Own losses = %.1f %%\n", self.Ncasualties/self.Nelements*100) - text=text..string.format("Targets = %d/%d\n", self.Ntargets, Ntargets) - text=text..string.format("Damage = %.1f %%\n", targetdamage) + text=text..string.format("Own casualties = %d/%d\n", self.Ncasualties, self.Nelements) + text=text..string.format("Own losses = %.1f %%\n", owndamage) + text=text..string.format("--------------------------\n") + text=text..string.format("Targets left = %d/%d\n", Ntargets, self.Ntargets) + text=text..string.format("Enemy losses = %.1f %%\n", targetdamage) + text=text..string.format("--------------------------\n") + --text=text..string.format("Loss ratio = %.1f %%\n", targetdamage) + --text=text..string.format("--------------------------\n") text=text..string.format("Success Cond = %s\n", tostring(successCondition)) text=text..string.format("Failure Cond = %s\n", tostring(failureCondition)) - text=text..string.format("Failed = %s", tostring(failed)) + text=text..string.format("--------------------------\n") + text=text..string.format("Final Success = %s\n", tostring(not failed)) + text=text..string.format("=========================") self:I(self.lid..text) if failed then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 423fdd7a3..b6a63be5c 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -817,7 +817,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) --- -- Task queue. - if #self.taskqueue>0 and self.verbose>1 then + if self.verbose>1 and #self.taskqueue>0 then local text=string.format("Tasks #%d", #self.taskqueue) for i,_task in pairs(self.taskqueue) do local task=_task --Ops.OpsGroup#OPSGROUP.Task @@ -1088,7 +1088,7 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) if element.unit and element.unit:IsAlive() then - local airbase=self:GetClosestAirbase() --element.unit:GetCoordinate():GetClosestAirbase() + local airbase=self:GetClosestAirbase() local parking=self:GetParkingSpot(element, 10, airbase) if airbase and parking then @@ -1096,18 +1096,13 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) self:T3(self.lid..string.format("EVENT: Element %s shut down engines ==> arrived", element.name)) else self:T3(self.lid..string.format("EVENT: Element %s shut down engines but is not parking. Is it dead?", element.name)) - --self:ElementDead(element) end else - --self:I(self.lid..string.format("EVENT: Element %s shut down engines but is NOT alive ==> waiting for crash event (==> dead)", element.name)) - end - else - -- element is nil - end + end -- element nil? end @@ -1154,8 +1149,7 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed", element.name)) - --self:ElementDead(element) + self:T3(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed", element.name)) self:ElementDestroyed(element) end @@ -1178,7 +1172,7 @@ function FLIGHTGROUP:OnEventRemoveUnit(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) + self:T3(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) self:ElementDead(element) end @@ -1406,7 +1400,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterSpawned(From, Event, To) - self:I(self.lid..string.format("Flight spawned!")) + self:T(self.lid..string.format("Flight spawned")) if self.ai then @@ -1543,7 +1537,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterAirborne(From, Event, To) - self:I(self.lid..string.format("Flight airborne")) + self:T(self.lid..string.format("Flight airborne")) if self.ai then self:_CheckGroupDone(1) @@ -1608,7 +1602,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterDead(From, Event, To) - self:I(self.lid..string.format("Flight dead!")) + self:T(self.lid..string.format("Flight dead!")) -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil @@ -1655,8 +1649,8 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) self:E(self.lid.."Update route denied. Group is DEAD!") allowed=false else - -- Not airborne yet. Try again in 1 sec. - self:I(self.lid.."FF update route denied ==> checking back in 5 sec") + -- Not airborne yet. Try again in 5 sec. + self:T(self.lid.."Update route denied ==> checking back in 5 sec") trepeat=-5 allowed=false end @@ -1673,7 +1667,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) local N=n or self.currentwp+1 if not N or N<1 then - self:E(self.lid.."FF update route denied because N=nil or N<1") + self:E(self.lid.."Update route denied because N=nil or N<1") trepeat=-5 allowed=false end @@ -1734,7 +1728,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) -- Debug info. local hb=self.homebase and self.homebase:GetName() or "unknown" local db=self.destbase and self.destbase:GetName() or "unknown" - self:I(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db)) + self:T(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db)) if #wp>1 then @@ -2207,7 +2201,7 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) local text=string.format("Flight group %s is HOLDING now", self.groupname) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) + self:T(self.lid..text) -- Add flight to waiting/holding queue. if self.flightcontrol then @@ -2936,18 +2930,13 @@ function FLIGHTGROUP:InitWaypoints() -- Template waypoints. self.waypoints0=self.group:GetTemplateRoutePoints() - - self:I(self.waypoints0) -- Waypoints self.waypoints={} for index,wp in pairs(self.waypoints0) do - - env.info("FF index "..index) - - local waypoint=self:_CreateWaypoint(wp) - + + local waypoint=self:_CreateWaypoint(wp) self:_AddWaypoint(waypoint) end @@ -2964,7 +2953,7 @@ function FLIGHTGROUP:InitWaypoints() end -- Debug info. - self:I(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) + self:T(self.lid..string.format("Initializing %d waypoints. Homebase %s ==> %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) -- Update route. if #self.waypoints>0 then @@ -2974,9 +2963,6 @@ function FLIGHTGROUP:InitWaypoints() self.passedfinalwp=true end - -- Update route (when airborne). - --self:_CheckGroupDone(1) - --self:__UpdateRoute(-1) end return self @@ -3251,12 +3237,6 @@ function FLIGHTGROUP:GetClosestAirbase() local airbase=coord:GetClosestAirbase() --(nil, coalition) - if airbase then - env.info("FF Got closest airbase ".. airbase:GetName()) - else - env.info("FF no closest airbase!") - end - return airbase end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index ab1af2f59..8cf2a117a 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -114,7 +114,7 @@ function NAVYGROUP:New(GroupName) self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. - self:AddTransition("*", "CollitionWarning", "*") -- Collision warning. + self:AddTransition("*", "CollisionWarning", "*") -- Collision warning. self:AddTransition("*", "ClearAhead", "*") -- Clear ahead. self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive. @@ -195,12 +195,14 @@ end -- @param #number WeaponType Type of weapon. Default auto. -- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task data. function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Clock, Prio) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) - self:AddTask(DCStask, Clock, nil, Prio) + local task=self:AddTask(DCStask, Clock, nil, Prio) + return task end --- Add a *scheduled* task. @@ -210,12 +212,14 @@ end -- @param #number WeaponType Type of weapon. Default auto. -- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task data. function NAVYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clock, Prio) local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack) - self:AddTask(DCStask, Clock, nil, Prio) - + local task=self:AddTask(DCStask, Clock, nil, Prio) + + return task end --- Add aircraft recovery time window and recovery case. @@ -409,11 +413,9 @@ function NAVYGROUP:onafterStatus(From, Event, To) end if not self.ispathfinding then - - - + if freepath<5000 then - self.ispathfinding=self:_FindPathToNextWaypoint() + --self.ispathfinding=self:_FindPathToNextWaypoint() end end @@ -466,7 +468,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) --- -- Task queue. - if #self.taskqueue>0 and self.verbose>1 then + if self.verbose>-1 and #self.taskqueue>0 then local text=string.format("Tasks #%d", #self.taskqueue) for i,_task in pairs(self.taskqueue) do local task=_task --Ops.OpsGroup#OPSGROUP.Task diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 08b99b8e9..2475b5677 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -120,13 +120,11 @@ OPSGROUP = { respawning = nil, wpcounter = 1, radio = {}, - radioDefault = {}, option = {}, optionDefault = {}, tacan = {}, icls = {}, callsign = {}, - callsignDefault = {}, } --- Status of group element. @@ -1234,7 +1232,7 @@ function OPSGROUP:AddTaskWaypoint(task, Waypoint, description, prio, duration) table.insert(self.taskqueue, newtask) -- Info. - self:I(self.lid..string.format("Adding WAYPOINT task %s at WP %d", newtask.description, newtask.waypoint)) + self:T(self.lid..string.format("Adding WAYPOINT task %s at WP ID=%d", newtask.description, newtask.waypoint)) self:T3({newtask=newtask}) -- Update route. @@ -2635,13 +2633,14 @@ end -- @param #number wpnumber Waypoint index/number. Default is as last waypoint. function OPSGROUP:_AddWaypoint(waypoint, wpnumber) + -- Index. wpnumber=wpnumber or #self.waypoints+1 - - self:I(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) -- Add waypoint to table. table.insert(self.waypoints, wpnumber, waypoint) + -- Debug info. + self:T2(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) end --- Initialize Mission Editor waypoints. @@ -3069,9 +3068,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) else - self:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive.") - end end @@ -3165,9 +3162,7 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) else - self:E(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") - end end @@ -3324,6 +3319,7 @@ end -- @return #OPSGROUP self function OPSGROUP:SetDefaultCallsign(CallsignName, CallsignNumber) + self.callsignDefault={} self.callsignDefault.NumberSquad=CallsignName self.callsignDefault.NumberGroup=CallsignNumber or 1 @@ -3344,9 +3340,8 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsign.NumberSquad, self.callsign.NumberGroup)) - local group=self.group --Wrapper.Group#GROUP - group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) + self.group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) end diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua index 13f38e89a..66ecfd6dd 100644 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ b/Moose Development/Moose/Ops/WingCommander.lua @@ -2,7 +2,8 @@ -- -- **Main Features:** -- --- * Stuff +-- * Manages AIRWINGS +-- * Handles missions (AUFTRAG) and finds the best airwing able to do the job -- -- === -- @@ -49,10 +50,7 @@ WINGCOMMANDER.version="0.1.0" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Define A2A and A2G parameters. -- TODO: Improve airwing selection. Mostly done! --- DONE: Add/remove spawned flightgroups to detection set. --- DONE: Borderzones. -- NOGO: Maybe it's possible to preselect the assets for the mission. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -76,9 +74,10 @@ function WINGCOMMANDER:New() -- From State --> Event --> To State self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. self:AddTransition("*", "Status", "*") -- Status report. - self:AddTransition("*", "MissionAssign", "*") -- Mission was assigned to an AIRWING. + self:AddTransition("*", "Stop", "Stopped") -- Stop WC. + + self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. self:AddTransition("*", "CancelMission", "*") -- Cancel mission. - self:AddTransition("*", "Defcon", "*") -- Cancel mission. ------------------------ --- Pseudo Functions --- @@ -217,6 +216,7 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) -- Mission queue. if #self.missionqueue>0 then + local text="Mission queue:" for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -226,6 +226,7 @@ function WINGCOMMANDER:onafterStatus(From, Event, To) text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) end self:I(self.lid..text) + end self:__Status(-30) @@ -235,14 +236,14 @@ end -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "MissionAssign" event. Mission is added to the AIRWING mission queue. +--- On after "AssignMission" event. Mission is added to the AIRWING mission queue. -- @param #WINGCOMMANDER self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Ops.AirWing#AIRWING Airwing The AIRWING. -- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterMissionAssign(From, Event, To, Airwing, Mission) +function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) Airwing:AddMission(Mission) @@ -268,7 +269,7 @@ function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) -- Airwing will cancel mission. if Mission.airwing then - Mission.airwing:MissionCancel(Mission) + Mission.airwing:CancelMission(Mission) end end @@ -300,7 +301,7 @@ function WINGCOMMANDER:CheckMissionQueue() if airwing then -- Add mission to airwing. - self:MissionAssign(airwing, mission) + self:AssignMission(airwing, mission) return end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index cc40a374b..322df4137 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1429,16 +1429,6 @@ end -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) - -- FireAtPoint = { - -- id = 'FireAtPoint', - -- params = { - -- point = Vec2, - -- radius = Distance, - -- expendQty = number, - -- expendQtyEnabled = boolean, - -- } - -- } - local DCSTask = { id = 'FireAtPoint', params = { @@ -1458,7 +1448,6 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType ) DCSTask.params.weaponType=WeaponType end - self:T3( { DCSTask } ) return DCSTask end From 495a9fd3a077f3da7dc894ff8fbc05f4681b1a5b Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 6 Aug 2020 01:43:42 +0200 Subject: [PATCH 29/79] Ops Target New Class. --- Moose Development/Moose/Core/Event.lua | 10 + Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/Target.lua | 620 ++++++++++++++++++++ 4 files changed, 632 insertions(+), 1 deletion(-) create mode 100644 Moose Development/Moose/Ops/Target.lua diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 156e26153..c19e41d4c 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1001,6 +1001,16 @@ function EVENT:onEvent( Event ) Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.initiator:isExist() and Event.IniDCSUnit:getTypeName() or "SCENERY" -- TODO: Bug fix for 2.1! end + + if Event.IniObjectCategory == Object.Category.BASE then + Event.IniDCSUnit = Event.initiator + Event.IniDCSUnitName = Event.IniDCSUnit:getName() + Event.IniUnitName = Event.IniDCSUnitName + Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName) + Event.IniCoalition = Event.IniDCSUnit:getCoalition() + Event.IniCategory = Event.IniDCSUnit:getDesc().category + Event.IniTypeName = Event.IniDCSUnit:getTypeName() + end end if Event.target then diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 1aaaff592..f74335862 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -73,6 +73,7 @@ __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Auftrag.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Target.lua' ) __Moose.Include( 'Scripts/Moose/Ops/OpsGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' ) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index b6a63be5c..f5300d295 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1643,7 +1643,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) if self:IsAlive() then -- and (self:IsAirborne() or self:IsWaiting() or self:IsInbound() or self:IsHolding()) then -- Alive & Airborne ==> Update route possible. - self:T3(self.lid.."Update route possible. Group is ALIVE and AIRBORNE or WAITING or INBOUND or HOLDING") + self:T3(self.lid.."Update route possible. Group is ALIVE") elseif self:IsDead() then -- Group is dead! No more updates. self:E(self.lid.."Update route denied. Group is DEAD!") diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua new file mode 100644 index 000000000..254f4d977 --- /dev/null +++ b/Moose Development/Moose/Ops/Target.lua @@ -0,0 +1,620 @@ +--- **Ops** - Target. +-- +-- **Main Features:** +-- +-- * Manages AIRWINGS +-- * Events when units get +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Target +-- @image OPS_Target.png + + +--- TARGET class. +-- @type TARGET +-- @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 #table targets Table of target objects. +-- @field #number targetcounter Running number to generate target object IDs. +-- @field #number life Total life points on last status update. +-- @field #number life0 Total life points of completely healthy targets. +-- @extends Core.Fsm#FSM + +--- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower +-- +-- === +-- +-- ![Banner Image](..\Presentations\WingCommander\TARGET_Main.jpg) +-- +-- # The TARGET Concept +-- +-- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned TARGET (mission). +-- +-- +-- @field #TARGET +TARGET = { + ClassName = "TARGET", + Debug = nil, + lid = nil, + targets = {}, + targetcounter = 0, + life = 0, + life0 = 0, +} + + +--- Type. +-- @type TARGET.ObjectType +-- @field #string UNIT Target is a UNIT object. +-- @field #string STATIC Target is a STATIC object. +-- @field #string COORDINATE Target is a COORDINATE. +-- @field #string AIRBASE Target is an AIRBASE. +TARGET.ObjectType={ + GROUP="Group", + UNIT="Unit", + STATIC="Static", + COORDINATE="Coordinate", + AIRBASE="Airbase", +} + +--- Type. +-- @type TARGET.ObjectStatus +-- @field #string ALIVE Object is alive. +-- @field #string DEAD Object is dead. +TARGET.ObjectStatus={ + ALIVE="Alive", + DEAD="Dead", +} +--- Type. +-- @type TARGET.Object +-- @field #number ID Target unique ID. +-- @field #string Name Target name. +-- @field #string Type Target type. +-- @field Wrapper.Positionable#POSITIONABLE Object. +-- @field #number Life Life points on last status update. +-- @field #number Life0 Life points of completely healthy target. +-- @field #string Status Status "Alive" or "Dead". + +_TARGETID=0 + +--- TARGET class version. +-- @field #string version +TARGET.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Improve airwing selection. Mostly done! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new TARGET object and start the FSM. +-- @param #TARGET self +-- @param #table TargetObject Target object. +-- @return #TARGET self +function TARGET:New(TargetObject) + + -- Inherit everything from INTEL class. + local self=BASE:Inherit(self, FSM:New()) --#TARGET + + -- Increase counter. + _TARGETID=_TARGETID+1 + + self.lid=string.format("TARGET #%03d | ", _TARGETID) + + self:AddObject(TargetObject) + + -- Start state. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Alive") -- Start FSM. + self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + self:AddTransition("*", "ObjectDamaged", "*") -- A Target was damaged. + self:AddTransition("*", "ObjectDestroyed", "*") -- A Target was destroyed. + self:AddTransition("*", "ObjectRemoved", "*") -- A Target was removed. + + self:AddTransition("*", "Damaged", "*") -- Target was damaged. + self:AddTransition("*", "Destroyed", "Dead") -- Target was completely destroyed. + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the TARGET. Initializes parameters and starts event handlers. + -- @function [parent=#TARGET] Start + -- @param #TARGET self + + --- Triggers the FSM event "Start" after a delay. Starts the TARGET. Initializes parameters and starts event handlers. + -- @function [parent=#TARGET] __Start + -- @param #TARGET self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the TARGET and all its event handlers. + -- @param #TARGET self + + --- Triggers the FSM event "Stop" after a delay. Stops the TARGET and all its event handlers. + -- @function [parent=#TARGET] __Stop + -- @param #TARGET self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#TARGET] Status + -- @param #TARGET self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#TARGET] __Status + -- @param #TARGET 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 + + -- Start. + self:__Start(-1) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create target data from a given object. +-- @param #TARGET self +-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE. +function TARGET:AddObject(Object) + + if Object:IsInstanceOf("GROUP") then + + local group=Object --Wrapper.Group#GROUP + + local units=group:GetUnits() + + for _,unit in pairs(units) do + self:_AddObject(unit) + end + + elseif Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") then + + local set=Object --Core.Set#SET_GROUP + + for _,object in pairs(set.Set) do + self:AddObject(object) + end + + + else + + --- + -- Units, Statics, Airbases, Coordinates + --- + + self:_AddObject(Object) + + end + +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Start & Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +-- @param #TARGET self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function TARGET:onafterStart(From, Event, To) + + -- Short info. + local text=string.format("Starting Target") + self:I(self.lid..text) + + self:HandleEvent(EVENTS.Dead, self.OnEventUnitDeadOrLost) + self:HandleEvent(EVENTS.UnitLost, self.OnEventUnitDeadOrLost) + + self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) + + self:__Status(-1) +end + +--- On after "Status" event. +-- @param #TARGET self +-- @param Wrapper.Group#GROUP Group Flight group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function TARGET:onafterStatus(From, Event, To) + + -- FSM state. + local fsmstate=self:GetState() + + -- Update damage. + local damaged=false + for i,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + local life=target.Life + target.Life=self:GetTargetLife(target) + if target.Life Trigger destroyed event. + if dead then + self:Destroyed() + end + +end + +--- On after "Damaged" event. +-- @param #TARGET self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function TARGET:onafterDamaged(From, Event, To) + + self:I(self.lid..string.format("Target damaged")) + +end + +--- On after "Destroyed" event. +-- @param #TARGET self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function TARGET:onafterDestroyed(From, Event, To) + + self:I(self.lid..string.format("Target destroyed")) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Event function handling the loss of a unit. +-- @param #TARGET self +-- @param Core.Event#EVENTDATA EventData Event data. +function TARGET:OnEventUnitDeadOrLost(EventData) + + -- Check that this is the right group. + if EventData and EventData.IniUnitName then + + -- Debug info. + --self:T3(self.lid..string.format("EVENT: Unit %s dead or lost!", EventData.IniUnitName)) + + -- Get target. + local target=self:GetTargetByName(EventData.IniUnitName) + + -- Check if this is one of ours. + if target and target.Status==TARGET.ObjectStatus.ALIVE then + + -- Debug message. + self:T3(self.lid..string.format("EVENT: target unit %s dead or lost ==> destroyed", target.Name)) + + -- Trigger object destroyed event. + self:ObjectDestroyed(target) + + end + + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Adding and Removing Targets +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create target data from a given object. +-- @param #TARGET self +-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE. +function TARGET:_AddObject(Object) + + local target={} --#TARGET.Object + + if Object:IsInstanceOf("UNIT") then + + local unit=Object --Wrapper.Unit#UNIT + + target.Type=TARGET.ObjectType.UNIT + target.Name=unit:GetName() + + if unit and unit:IsAlive() then + target.Life=unit:GetLife() + target.Life0=math.max(unit:GetLife0(), target.Life) -- There was an issue with ships that life is greater life0! + end + + elseif Object:IsInstanceOf("STATIC") then + + local static=Object --Wrapper.Static#STATIC + + target.Type=TARGET.ObjectType.STATIC + target.Name=static:GetName() + + if static and static:IsAlive() then + target.Life0=1 + target.Life=1 + end + + + elseif Object:IsInstanceOf("AIRBASE") then + + local airbase=Object --Wrapper.Airbase#AIRBASE + + target.Type=TARGET.ObjectType.AIRBASE + target.Name=airbase:GetName() + + target.Life0=1 + target.Life=1 + + + elseif Object:IsInstanceOf("COORDINATE") then + + local coord=Object --Core.Point#COORDINATE + + target.Type=TARGET.ObjectType.COORDINATE + target.Name=coord:ToStringMGRS() + + target.Life0=1 + target.Life=1 + + else + self:E(self.lid.."ERROR: Unknown object type!") + return nil + end + + self.life=self.life+target.Life + self.life0=self.life0+target.Life0 + + -- Increase counter. + self.targetcounter=self.targetcounter+1 + + target.ID=self.targetcounter + target.Status=TARGET.ObjectStatus.ALIVE + target.Object=Object + + table.insert(self.targets, target) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Life and Damage Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get target life points. +-- @param #TARGET self +-- @return #number Number of initial life points when mission was planned. +function TARGET:GetLife0() + return self.life0 +end + +--- Get current damage. +-- @param #TARGET self +-- @return #number Damage in percent. +function TARGET:GetDamage() + local life=self:GetLife()/self:GetLife0() + local damage=1-life + return damage*100 +end + +--- Get target life points. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return #number Life points of target. +function TARGET:GetTargetLife(Target) + + if Target.Type==TARGET.ObjectType.UNIT then + + if Target.Object and Target.Object:IsAlive() then + return Target.Object:GetLife() + else + return 0 + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + if Target.Object and Target.Object:IsAlive() then + return 1 + else + return 0 + end + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + if Target.Status==TARGET.ObjectStatus.ALIVE then + return 1 + else + return 0 + end + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + return 1 + + end + +end + +--- Get current life points. +-- @param #TARGET self +-- @return #number Life points of target. +function TARGET:GetLife() + + local N=0 + + local function _GetLife(unit) + local unit=unit --Wrapper.Unit#UNIT + if Healthy then + local life=unit:GetLife() + local life0=unit:GetLife0() + + return math.max(life, life0) + else + return unit:GetLife() + end + end + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + N=N+self:GetTargetLife(Target) + + end + + return N +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get a target object by its name. +-- @param #TARGET self +-- @param #string ObjectName Object name. +-- @return #TARGET.Object The target object table or nil. +function TARGET:GetTargetByName(ObjectName) + + for _,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + if ObjectName==target.Name then + return target + end + end + + return nil +end + + +--- Count alive targets. +-- @param #TARGET self +-- @return #number Number of alive target objects. +function TARGET:CountTargets() + + local N=0 + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + if Target.Type==TARGET.ObjectType.UNIT then + + local target=Target.Object --Wrapper.Unit#UNIT + + if target and target:IsAlive() and target:GetLife()>1 then + N=N+1 + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + local target=Target.Object --Wrapper.Static#STATIC + + if target and target:IsAlive() then + N=N+1 + end + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + if Target.Status==TARGET.ObjectStatus.ALIVE then + N=N+1 + end + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + -- No target we can check! + + else + self:E(self.lid.."ERROR unknown target type") + end + + end + + return N +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 337e1a837f6a0687d5a6ca2e25277cb8697c1350 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 8 Aug 2020 01:10:03 +0200 Subject: [PATCH 30/79] Ops --- .../Moose/Functional/Warehouse.lua | 7 + Moose Development/Moose/Ops/AirWing.lua | 115 ++- Moose Development/Moose/Ops/Auftrag.lua | 704 ++++++------------ Moose Development/Moose/Ops/FlightGroup.lua | 7 +- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Ops/Squadron.lua | 47 +- Moose Development/Moose/Ops/Target.lua | 278 ++++++- Moose Development/Moose/Wrapper/Group.lua | 39 + 8 files changed, 618 insertions(+), 581 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 297082e22..98bce2200 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1609,6 +1609,7 @@ WAREHOUSE = { -- @field #boolean iscargo If true, asset is cargo. If false asset is transport. Nil if in stock. -- @field #number rid The request ID of this asset. -- @field #boolean arrived If true, asset arrived at its destination. +-- @field #number damage Damage of asset group in percent. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -3821,6 +3822,11 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu asset.spawned=false asset.iscargo=nil asset.arrived=nil + + -- Destroy group if it is alive. + if group:IsAlive()==true then + asset.damage=group:GetDamage() + end -- Add asset to stock. table.insert(self.stock, asset) @@ -3991,6 +3997,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, asset.skill=skill asset.assignment=assignment asset.spawned=false + asset.damage=0 asset.spawngroupname=string.format("%s_AID-%d", templategroupname, asset.uid) if i==1 then diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index e87de00e1..2d2d28ae7 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -66,7 +66,7 @@ -- At this point the airwing does not have any assets (aircraft). In order to add these, one needs to first define SQUADRONS. -- -- VFA151=SQUADRON:New("F-14 Group", 8, "VFA-151 (Vigilantes)") --- VFA151:AddMissionCapability({AUFTRAG.Type.PATROL, AUFTRAG.Type.INTERCEPT}) +-- VFA151:AddMissionCapability({AUFTRAG.Type.GCCAP, AUFTRAG.Type.INTERCEPT}) -- -- airwing:AddSquadron(VFA151) -- @@ -78,8 +78,8 @@ -- defined in the Mission Editor. -- -- -- F-14 payloads for CAP and INTERCEPT. Phoenix are first, sparrows are second choice. --- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROL}, 80) --- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.PATROL}) +-- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCCAP}, 80) +-- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCCAP}) -- -- This will add two AIM-54C and 20 AIM-7M payloads. -- @@ -127,6 +127,7 @@ AIRWING = { -- @field #AIRWING.Payload payload The payload of the asset. -- @field Ops.FlightGroup#FLIGHTGROUP flightgroup The flightgroup object. -- @field #string squadname Name of the squadron this asset belongs to. +-- @field #number Treturned Time stamp when asset returned to the airwing. -- @extends Functional.Warehouse#WAREHOUSE.Assetitem --- Payload data. @@ -284,26 +285,32 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) Performance=Performance or 50 if type(Unit)=="string" then - Unit=UNIT:FindByName(Unit) + local name=Unit + env.info("unit as string "..Unit) + Unit=UNIT:FindByName(name) if not Unit then - Unit=GROUP:FindByName(Unit) + env.info("no UNIT trying group") + Unit=GROUP:FindByName(name) + if not Unit then + env.info("no GROUP either!") + end end end - -- If a GROUP object was given, get the first unit. - if Unit:IsInstanceOf("GROUP") then - Unit=Unit:GetUnit(1) - end - - -- Ensure Missiontypes is a table. - if MissionTypes and type(MissionTypes)~="table" then - MissionTypes={MissionTypes} - end - if Unit then + + -- If a GROUP object was given, get the first unit. + if Unit:IsInstanceOf("GROUP") then + Unit=Unit:GetUnit(1) + end + + -- Ensure Missiontypes is a table. + if MissionTypes and type(MissionTypes)~="table" then + MissionTypes={MissionTypes} + end - local payload={} --#AIRWING.Payload - + -- Create payload. + local payload={} --#AIRWING.Payload payload.unitname=Unit:GetName() payload.aircrafttype=Unit:GetTypeName() payload.pylons=Unit:GetTemplatePayload() @@ -338,8 +345,10 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) table.insert(self.payloads, payload) return payload + end + self:E(self.lid.."ERROR: No UNIT found to create PAYLOAD!") return nil end @@ -771,7 +780,7 @@ function AIRWING:onafterStatus(From, Event, To) -- General info: -- TODO: assets total - local text=string.format("Status %s: missions=%d, payloads=%d (%d), squads=%d", fsmstate, nmissions, #self.payloads, Npayloads, #self.squadrons) + local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d", fsmstate, nmissions, Npayloads, #self.payloads, #self.squadrons) self:I(self.lid..text) ------------------ @@ -898,7 +907,7 @@ end -- @return #AIRWING self function AIRWING:CheckCAP() - local Ncap=self:CountMissionsInQueue({AUFTRAG.Type.PATROL, AUFTRAG.Type.INTERCEPT}) + local Ncap=self:CountMissionsInQueue({AUFTRAG.Type.GCCAP, AUFTRAG.Type.INTERCEPT}) for i=1,self.nflightsCAP-Ncap do @@ -906,7 +915,7 @@ function AIRWING:CheckCAP() local altitude=patrol.altitude+1000*patrol.noccupied - local missionCAP=AUFTRAG:NewPATROL(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) + local missionCAP=AUFTRAG:NewGCCAP(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) missionCAP.patroldata=patrol @@ -1068,7 +1077,7 @@ function AIRWING:GetTankerForFlight(flightgroup) end ---- Get next mission. +--- Check if mission is not over and ready to cancel. -- @param #AIRWING self function AIRWING:_CheckMissions() @@ -1076,12 +1085,8 @@ function AIRWING:_CheckMissions() for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission:IsNotOver() then - - if mission:IsReadyToCancel() then - mission:Cancel() - end - + if mission:IsNotOver() and mission:IsReadyToCancel() then + mission:Cancel() end end @@ -1157,7 +1162,7 @@ function AIRWING:_GetNextMission() -- Another check. if #assets #Assets then self:I(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 0ef2d07e1..cb7944935 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -51,7 +51,7 @@ -- @field #number orbitLeg Length of orbit leg in meters. -- @field Core.Point#COORDINATE orbitRaceTrack Race-track orbit coordinate. -- --- @field #AUFTRAG.TargetData engageTarget Target data to engage. +-- @field Ops.Target#TARGET engageTarget Target data to engage. -- -- @field Core.Zone#ZONE_RADIUS engageZone *Circular* engagement zone. -- @field #table engageTargetTypes Table of target types that are engaged in the engagement zone. @@ -98,11 +98,6 @@ -- @field #number missionRepeated Number of times mission was repeated. -- @field #number missionRepeatMax Number of times mission is repeated if failed. -- --- @field #number radioFreq Mission radio frequency in MHz. --- @field #number radioModu Mission radio modulation (0=AM and 1=FM). --- @field #number tacanChannel Mission TACAN channel. --- @field #number tacanMorse Mission TACAN morse code. --- -- @field Ops.OpsGroup#OPSGROUP.Radio radio Radio freq and modulation. -- @field Ops.OpsGroup#OPSGROUP.Beacon tacan TACAN setting. -- @field Ops.OpsGroup#OPSGROUP.Beacon icls ICLS setting. @@ -188,9 +183,9 @@ -- -- An orbit mission can be created with the @{#AUFTRAG.NewORBIT}() function. -- --- ## PATROL +-- ## GCCAP -- --- An patrol mission can be created with the @{#AUFTRAG.NewPATROL}() function. +-- An patrol mission can be created with the @{#AUFTRAG.NewGCCAP}() function. -- -- ## RECON -- @@ -293,7 +288,7 @@ _AUFTRAGSNR=0 -- @field #string FERRY Ferry flight mission. -- @field #string INTERCEPT Intercept mission. -- @field #string ORBIT Orbit mission. --- @field #string PATROL Similar to CAP but no auto engage targets. +-- @field #string GCCAP Similar to CAP but no auto engage targets. -- @field #string RECON Recon mission. -- @field #string RECOVERYTANKER Recovery tanker mission. Not implemented yet. -- @field #string RESCUEHELO Rescue helo. @@ -316,7 +311,7 @@ AUFTRAG.Type={ FERRY="Ferry Flight", INTERCEPT="Intercept", ORBIT="Orbit", - PATROL="Patrol", + GCCAP="Patrol", RECON="Recon", RECOVERYTANKER="Recovery Tanker", RESCUEHELO="Rescue Helo", @@ -632,7 +627,7 @@ function AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) return mission end ---- Create a PATROL mission. +--- Create a GCCAP mission. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -640,13 +635,13 @@ end -- @param #number Heading Heading of race-track pattern in degrees. Default random in [0, 360) degrees. -- @param #number Leg Length of race-track in NM. Default 10 NM. -- @return #AUFTRAG self -function AUFTRAG:NewPATROL(Coordinate, Altitude, Speed, Heading, Leg) +function AUFTRAG:NewGCCAP(Coordinate, Altitude, Speed, Heading, Leg) -- Create ORBIT first. local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) - -- Mission type PATROL. - mission.type=AUFTRAG.Type.PATROL + -- Mission type GCCAP. + mission.type=AUFTRAG.Type.GCCAP mission:_SetLogID() @@ -671,7 +666,7 @@ function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSyst -- Create ORBIT first. local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) - -- Mission type PATROL. + -- Mission type TANKER. mission.type=AUFTRAG.Type.TANKER mission:_SetLogID() @@ -701,7 +696,7 @@ function AUFTRAG:NewAWACS(Coordinate, Altitude, Speed, Heading, Leg) -- Create ORBIT first. local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) - -- Mission type PATROL. + -- Mission type AWACS. mission.type=AUFTRAG.Type.AWACS mission:_SetLogID() @@ -1082,7 +1077,7 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) - mission:_TargetFromObject(Carrier) + self.carrier=Carrier -- Mission options: mission.missionTask=ENUMS.MissionTask.NOTHING @@ -1157,6 +1152,31 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius) return mission end +--- Create a mission to attack a group. Mission type is automatically chosen from the group category. +-- @param #AUFTRAG self +-- @param Ops.Target#TARGET Target The target. +-- @return #AUFTRAG self +function AUFTRAG:NewTARGET(Target) + + local mission=nil --#AUFTRAG + + if Target.category==TARGET.Category.GROUND then + + + elseif Target.category==TARGET.Category.AIRCRAFT then + + elseif Target.category==TARGET.Category.AIRBASE then + + elseif Target.category==TARGET.Category.COORDINATE then + + end + + + if mission then + mission:SetPriority(10, true) + end + +end --- Create a mission to attack a group. Mission type is automatically chosen from the group category. -- @param #AUFTRAG self @@ -1882,12 +1902,6 @@ function AUFTRAG:Evaluate() -- Assume success and check if any failed condition applies. local failed=false - -- Any success condition true? - local successCondition=self:EvalConditionsAny(self.conditionSuccess) - - -- Any failure condition true? - local failureCondition=self:EvalConditionsAny(self.conditionFailure) - -- Target damage in %. local targetdamage=self:GetTargetDamage() @@ -1931,6 +1945,13 @@ function AUFTRAG:Evaluate() end + + -- Any success condition true? + local successCondition=self:EvalConditionsAny(self.conditionSuccess) + + -- Any failure condition true? + local failureCondition=self:EvalConditionsAny(self.conditionFailure) + if failureCondition then failed=true elseif successCondition then @@ -2217,8 +2238,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Ops.OpsGroup#OPSGROUP FlightGroup -function AUFTRAG:onafterScheduled(From, Event, To, FlightGroup) +function AUFTRAG:onafterScheduled(From, Event, To) self.status=AUFTRAG.Status.SCHEDULED self:T(self.lid..string.format("New mission status=%s", self.status)) end @@ -2264,6 +2284,7 @@ end -- @param #string To To state. -- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group that is dead now. function AUFTRAG:onafterElementDestroyed(From, Event, To, OpsGroup, Element) + -- Increase number of own casualties. self.Ncasualties=self.Ncasualties+1 end @@ -2499,6 +2520,170 @@ function AUFTRAG:onafterStop(From, Event, To) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Target Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create target data from a given object. +-- @param #AUFTRAG self +-- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC. +function AUFTRAG:_TargetFromObject(Object) + + if not self.engageTarget then + + self.engageTarget=TARGET:New(Object) + + else + + -- Target was already specified elsewhere. + + end + + -- TODO: get rid of this. + self.Ntargets=self.engageTarget.Ntargets0 + + -- Debug info. + self:T(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", self.engageTarget.lid, self.engageTarget.lid, self.Ntargets, self.engageTarget:GetLife())) + + return self +end + + +--- Count alive mission targets. +-- @param #AUFTRAG self +-- @param #AUFTRAG.TargetData Target (Optional) The target object. +-- @return #number Number of alive target units. +function AUFTRAG:CountMissionTargets(Target) + + if self.engageTarget then + return self.engageTarget:CountTargets() + else + return 0 + end + +end + +--- Get target life points. +-- @param #AUFTRAG self +-- @return #number Number of initial life points when mission was planned. +function AUFTRAG:GetTargetInitialLife() + local target=self:GetTargetData() + if target then + return target.life0 + else + return 0 + end +end + +--- Get target damage. +-- @param #AUFTRAG self +-- @return #number Damage in percent. +function AUFTRAG:GetTargetDamage() + local target=self:GetTargetData() + if target then + return target:GetDamage() + else + return 0 + end +end + + +--- Get target life points. +-- @param #AUFTRAG self +-- @return #number Life points of target. +function AUFTRAG:GetTargetLife() + local target=self:GetTargetData() + if target then + return target:GetLife() + else + return 0 + end +end + +--- Get target. +-- @param #AUFTRAG self +-- @return Ops.Target#TARGET The target object. Could be many things. +function AUFTRAG:GetTargetData() + return self.engageTarget +end + +--- Get mission objective object. Could be many things depending on the mission type. +-- @param #AUFTRAG self +-- @return Wrapper.Positionable#POSITIONABLE The target object. Could be many things. +function AUFTRAG:GetObjective() + return self:GetTargetData().Target +end + +--- Get type of target. +-- @param #AUFTRAG self +-- @return #string The target type. +function AUFTRAG:GetTargetType() + return self:GetTargetData().Type +end + +--- Get 2D vector of target. +-- @param #AUFTRAG self +-- @return DCS#VEC2 The target 2D vector or *nil*. +function AUFTRAG:GetTargetVec2() + local coord=self:GetTargetCoordinate() + if coord then + return coord:GetVec2() + end + return nil +end + +--- Get coordinate of target. +-- @param #AUFTRAG self +-- @return Core.Point#COORDINATE The target coordinate or *nil*. +function AUFTRAG:GetTargetCoordinate() + + if self.transportPickup then + + -- Special case where we defined a + return self.transportPickup + + elseif self.engageTarget then + + return self.engageTarget:GetCoordinate() + + else + self:E(self.lid.."ERROR: Cannot get target coordinate!") + end + + return nil +end + +--- Get name of the target. +-- @param #AUFTRAG self +-- @return #string Name of the target or "N/A". +function AUFTRAG:GetTargetName() + + if self.engageTarget.Target then + return self.engageTarget.Name + end + + return "N/A" +end + + +--- Get distance to target. +-- @param #AUFTRAG self +-- @param Core.Point#COORDINATE FromCoord The coordinate from which the distance is measured. +-- @return #number Distance in meters or 0. +function AUFTRAG:GetTargetDistance(FromCoord) + + local TargetCoord=self:GetTargetCoordinate() + + if TargetCoord and FromCoord then + return TargetCoord:Get2DDistance(FromCoord) + else + self:E(self.lid.."ERROR: TargetCoord or FromCoord does not exist in AUFTRAG:GetTargetDistance() function! Returning 0") + end + + return 0 +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2554,229 +2739,6 @@ function AUFTRAG:GetAssetByName(Name) return nil end - ---- Count alive mission targets. --- @param #AUFTRAG self --- @param #AUFTRAG.TargetData Target (Optional) The target object. --- @return #number Number of alive target units. -function AUFTRAG:CountMissionTargets(Target) - - local N=0 - - Target=Target or self:GetTargetData() - - if Target then - - if Target.Type==AUFTRAG.TargetType.GROUP then - - local target=Target.Target --Wrapper.Group#GROUP - - local units=target:GetUnits() - - for _,_unit in pairs(units or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - -- We check that unit is "alive" and has health >1. Somtimes units get heavily damanged but are still alive. - -- TODO: here I could introduce and count that if units have only health < 50% if mission objective is to just "damage" the units. - if unit and unit:IsAlive() and unit:GetLife()>1 then - N=N+1 - end - end - - elseif Target.Type==AUFTRAG.TargetType.UNIT then - - local target=Target.Target --Wrapper.Unit#UNIT - - if target and target:IsAlive() and target:GetLife()>1 then - N=N+1 - end - - elseif Target.Type==AUFTRAG.TargetType.STATIC then - - local target=Target.Target --Wrapper.Static#STATIC - - if target and target:IsAlive() then - N=N+1 - end - - elseif Target.Type==AUFTRAG.TargetType.AIRBASE then - - -- TODO: any (good) way to tell whether an airbase was "destroyed" or at least damaged? Is :GetLive() working? - - elseif Target.Type==AUFTRAG.TargetType.COORDINATE then - - -- No target! - - elseif Target.Type==AUFTRAG.TargetType.SETGROUP then - - for _,_group in pairs(Target.Target.Set or {}) do - local group=_group --Wrapper.Group#GROUP - - local units=group:GetUnits() - - for _,_unit in pairs(units or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - -- We check that unit is "alive". - if unit and unit:IsAlive() and unit:GetLife()>1 then - N=N+1 - end - end - - end - - elseif Target.Type==AUFTRAG.TargetType.SETUNIT then - - for _,_unit in pairs(Target.Target.Set or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - -- We check that unit is "alive". - if unit and unit:IsAlive() and unit:GetLife()>1 then - N=N+1 - end - - end - - else - self:E("ERROR unknown target type") - end - end - - return N -end - ---- Get target life points. --- @param #AUFTRAG self --- @return #number Number of initial life points when mission was planned. -function AUFTRAG:GetTargetInitialLife() - return self:GetTargetData().Lifepoints -end - ---- Get target damage. --- @param #AUFTRAG self --- @return #number Damage in percent. -function AUFTRAG:GetTargetDamage() - local target=self:GetTargetData() - local life=self:GetTargetLife()/self:GetTargetInitialLife() - local damage=1-life - return damage*100 -end - - ---- Get target life points. --- @param #AUFTRAG self --- @return #number Life points of target. -function AUFTRAG:GetTargetLife() - return self:_GetTargetLife(nil, false) -end - ---- Get target life points. --- @param #AUFTRAG self --- @param #AUFTRAG.TargetData Target (Optional) The target object. --- @param #boolean Healthy Get the life points of the healthy target. --- @return #number Life points of target. -function AUFTRAG:_GetTargetLife(Target, Healthy) - - local N=0 - - Target=Target or self:GetTargetData() - - local function _GetLife(unit) - local unit=unit --Wrapper.Unit#UNIT - if Healthy then - local life=unit:GetLife() - local life0=unit:GetLife0() - - return math.max(life, life0) - else - return unit:GetLife() - end - end - - if Target then - - if Target.Type==AUFTRAG.TargetType.GROUP then - - local target=Target.Target --Wrapper.Group#GROUP - - local units=target:GetUnits() - - for _,_unit in pairs(units or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - -- We check that unit is "alive". - if unit and unit:IsAlive() then - N=N+_GetLife(unit) - end - end - - elseif Target.Type==AUFTRAG.TargetType.UNIT then - - local target=Target.Target --Wrapper.Unit#UNIT - - if target and target:IsAlive() then - N=N+_GetLife(target) - end - - elseif Target.Type==AUFTRAG.TargetType.STATIC then - - local target=Target.Target --Wrapper.Static#STATIC - - -- Statics are alive or not. - if target and target:IsAlive() then - N=N+1 --_GetLife(target) - else - N=N+0 - end - - elseif Target.Type==AUFTRAG.TargetType.AIRBASE then - - -- TODO: any (good) way to tell whether an airbase was "destroyed" or at least damaged? Is :GetLive() working? - N=N+1 - - elseif Target.Type==AUFTRAG.TargetType.COORDINATE then - - -- A coordinate does not live. - N=N+1 - - elseif Target.Type==AUFTRAG.TargetType.SETGROUP then - - for _,_group in pairs(Target.Target.Set or {}) do - local group=_group --Wrapper.Group#GROUP - - local units=group:GetUnits() - - for _,_unit in pairs(units or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - -- We check that unit is "alive". - if unit and unit:IsAlive() then - N=N+_GetLife(unit) - end - end - - end - - elseif Target.Type==AUFTRAG.TargetType.SETUNIT then - - for _,_unit in pairs(Target.Target.Set or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - -- We check that unit is "alive". - if unit and unit:IsAlive() then - N=N+_GetLife(unit) - end - - end - - else - self:E(self.lid.."ERROR unknown target type") - end - end - - return N -end - --- Count alive flight groups assigned for this mission. -- @param #AUFTRAG self -- @return #number Number of alive flight groups. @@ -2791,109 +2753,6 @@ function AUFTRAG:CountOpsGroups() return N end ---- Get coordinate of target. --- @param #AUFTRAG self --- @return #AUFTRAG.TargetData The target object. Could be many things. -function AUFTRAG:GetTargetData() - return self.engageTarget -end - ---- Get mission objective object. Could be many things depending on the mission type. --- @param #AUFTRAG self --- @return Wrapper.Positionable#POSITIONABLE The target object. Could be many things. -function AUFTRAG:GetObjective() - return self:GetTargetData().Target -end - ---- Get type of target. --- @param #AUFTRAG self --- @return #string The target type. -function AUFTRAG:GetTargetType() - return self:GetTargetData().Type -end - ---- Get 2D vector of target. --- @param #AUFTRAG self --- @return DCS#VEC2 The target 2D vector or *nil*. -function AUFTRAG:GetTargetVec2() - local coord=self:GetTargetCoordinate() - if coord then - return coord:GetVec2() - end - return nil -end - ---- Get coordinate of target. --- @param #AUFTRAG self --- @return Core.Point#COORDINATE The target coordinate or *nil*. -function AUFTRAG:GetTargetCoordinate() - - if self.transportPickup then - - -- Special case where we defined a - return self.transportPickup - - else - - local target - - if self:GetTargetType()==AUFTRAG.TargetType.COORDINATE then - - -- Here the objective itself is a COORDINATE. - return self:GetObjective() - - elseif self:GetTargetType()==AUFTRAG.TargetType.SETGROUP then - - -- Return the first group in the set. - -- TODO: does this only return ALIVE groups?! - return self:GetObjective():GetFirst():GetCoordinate() - - elseif self:GetTargetType()==AUFTRAG.TargetType.SETUNIT then - - -- Return the first unit in the set. - -- TODO: does this only return ALIVE units?! - return self:GetObjective():GetFirst():GetCoordinate() - - else - - -- In all other cases the GetCoordinate() function should work. - return self:GetObjective():GetCoordinate() - - end - end - - return nil -end - ---- Get name of the target. --- @param #AUFTRAG self --- @return #string Name of the target or "N/A". -function AUFTRAG:GetTargetName() - - if self.engageTarget.Target then - return self.engageTarget.Name - end - - return "N/A" -end - - ---- Get distance to target. --- @param #AUFTRAG self --- @param Core.Point#COORDINATE FromCoord The coordinate from which the distance is measured. --- @return #number Distance in meters or 0. -function AUFTRAG:GetTargetDistance(FromCoord) - - local TargetCoord=self:GetTargetCoordinate() - - if TargetCoord and FromCoord then - return TargetCoord:Get2DDistance(FromCoord) - else - self:E(self.lid.."ERROR: TargetCoord or FromCoord does not exist in AUFTRAG:GetTargetDistance() function! Returning 0") - end - - return 0 -end --- Get coordinate of target. First unit/group of the set is used. -- @param #AUFTRAG self @@ -3100,10 +2959,10 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- Done below as also other mission types use the orbit task. - elseif self.type==AUFTRAG.Type.PATROL then + elseif self.type==AUFTRAG.Type.GCCAP then -------------------- - -- PATROL Mission -- + -- GCCAP Mission -- -------------------- -- Done below as also other mission types use the orbit task. @@ -3177,7 +3036,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP. local param={} - param.unitname=self:GetTargetName() + param.unitname=self.carrier:GetName() param.offsetX=200 param.offsetZ=240 param.altitude=70 @@ -3207,7 +3066,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) if self.type==AUFTRAG.Type.ORBIT or self.type==AUFTRAG.Type.CAP or self.type==AUFTRAG.Type.CAS or - self.type==AUFTRAG.Type.PATROL or + self.type==AUFTRAG.Type.GCCAP or self.type==AUFTRAG.Type.AWACS or self.type==AUFTRAG.Type.TANKER then @@ -3237,136 +3096,35 @@ end --- Get DCS task table for an attack group or unit task. -- @param #AUFTRAG self --- @param #AUFTRAG.TargetData target Target data. +-- @param Ops.Target#TARGET Target Target data. -- @param #table DCStasks DCS DCS tasks table to which the task is added. -- @return DCS#Task The DCS task table. -function AUFTRAG:_GetDCSAttackTask(target, DCStasks) +function AUFTRAG:_GetDCSAttackTask(Target, DCStasks) - local DCStask=nil + DCStasks=DCStasks or {} + + for _,_target in pairs(Target.targets) do + local target=_target --Ops.Target#TARGET.Object - if target.Type==AUFTRAG.TargetType.GROUP then - - DCStask=CONTROLLABLE.TaskAttackGroup(nil, target.Target, self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageAsGroup) - - table.insert(DCStasks, DCStask) + if target.Type==TARGET.ObjectType.GROUP then - elseif target.Type==AUFTRAG.TargetType.UNIT or target.Type==AUFTRAG.TargetType.STATIC then - - DCStask=CONTROLLABLE.TaskAttackUnit(nil, target.Target, self.engageAsGroup, self.WeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) - - table.insert(DCStasks, DCStask) - - elseif target.Type==AUFTRAG.TargetType.SETGROUP then - - -- Add all groups. - for _,group in pairs(target.Target.Set or {}) do - DCStask=CONTROLLABLE.TaskAttackGroup(nil, group, self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageAsGroup) + local DCStask=CONTROLLABLE.TaskAttackGroup(nil, target.Object, self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageAsGroup) + table.insert(DCStasks, DCStask) - end - - elseif target.Type==AUFTRAG.TargetType.SETUNIT then - - -- Add tasks to attack all units. - for _,unit in pairs(target.Target.Set or {}) do - DCStask=CONTROLLABLE.TaskAttackUnit(nil, unit, self.engageAsGroup, self.WeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) + + elseif target.Type==TARGET.ObjectType.UNIT or target.Type==TARGET.ObjectType.STATIC then + + local DCStask=CONTROLLABLE.TaskAttackUnit(nil, target.Object, self.engageAsGroup, self.WeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) + table.insert(DCStasks, DCStask) + end - + end - + return DCStasks end ---- Create target data from a given object. --- @param #AUFTRAG self --- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC. --- @return #AUFTRAG.TargetData Target. -function AUFTRAG:_TargetFromObject(Object) - - local target={} --#AUFTRAG.TargetData - - -- The object. - target.Target=Object - - if Object:IsInstanceOf("GROUP") then - - target.Type=AUFTRAG.TargetType.GROUP - - local object=Object --Wrapper.Group#GROUP - - target.Name=object:GetName() - - elseif Object:IsInstanceOf("UNIT") then - - target.Type=AUFTRAG.TargetType.UNIT - - local object=Object --Wrapper.Unit#UNIT - - target.Name=object:GetName() - - elseif Object:IsInstanceOf("STATIC") then - - target.Type=AUFTRAG.TargetType.STATIC - - target.Name=Object:GetName() - - elseif Object:IsInstanceOf("COORDINATE") then - - target.Type=AUFTRAG.TargetType.COORDINATE - - local object=Object --Core.Point#COORDINATE - - target.Name=object:ToStringLLDMS() - - elseif Object:IsInstanceOf("AIRBASE") then - - target.Type=AUFTRAG.TargetType.AIRBASE - - local object=Object --Wrapper.Airbase#AIRBASE - - target.Name=object:GetName() - - elseif Object:IsInstanceOf("SET_GROUP") then - - target.Type=AUFTRAG.TargetType.SETGROUP - - local object=Object --Core.Set#SET_GROUP - - target.Name=object:GetFirst():GetName() - - elseif Object:IsInstanceOf("SET_UNIT") then - - target.Type=AUFTRAG.TargetType.SETUNIT - - local object=Object --Core.Set#SET_UNIT - - target.Name=object:GetFirst():GetName() - - else - self:E(self.lid.."ERROR: Unknown object given as target. Needs to be a GROUP, UNIT, STATIC, COORDINATE") - return nil - end - - - -- Number of initial targets. - local Ninitial=self:CountMissionTargets(target) - - -- Initial total life point. - local Lifepoints=self:_GetTargetLife(target, true) - - -- Set engage Target. - self.engageTarget=target - self.engageTarget.Ninital=Ninitial - self.engageTarget.Lifepoints=Lifepoints - - -- TODO: get rid of this. - self.Ntargets=Ninitial - - -- Debug info. - self:T(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", target.Name, target.Type, Ninitial, Lifepoints)) - - return target -end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f5300d295..34aaaa818 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2624,6 +2624,9 @@ function FLIGHTGROUP:AddElementByName(unitname) element.status=OPSGROUP.ElementStatus.INUTERO element.group=unit:GetGroup() + -- TODO: this is wrong when grouping is used! + local unittemplate=element.unit:GetTemplate() + element.modex=element.unit:GetTemplate().onboard_num element.skill=element.unit:GetTemplate().skill element.pylons=element.unit:GetTemplatePylons() @@ -2642,8 +2645,8 @@ function FLIGHTGROUP:AddElementByName(unitname) element.ai=true end - local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d %%), category=%d, categoryname=%s, callsign=%s, ai=%s", - element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel, element.category, element.categoryname, element.callsign, tostring(element.ai)) + local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", + element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) self:I(self.lid..text) -- Add element to table. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2475b5677..0ce61fdc7 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1437,7 +1437,7 @@ end function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Debug message. - local text=string.format("Task %s ID=%d execute.", tostring(Task.description), Task.id) + local text=string.format("Task %s ID=%d execute", tostring(Task.description), Task.id) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 499863a46..88261aa7c 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -26,6 +26,8 @@ -- @field #number ngrouping User defined number of units in the asset group. -- @field #table assets Squadron assets. -- @field #table missiontypes Capabilities (mission types and performances) of the squadron. +-- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight. +-- @field #number repairtime Time in seconds for each -- @field #string livery Livery of the squadron. -- @field #number skill Skill of squadron members. -- @field #number modex Modex. @@ -67,6 +69,8 @@ SQUADRON = { aircrafttype = nil, assets = {}, missiontypes = {}, + repairtime = 0, + maintenancetime= 0, livery = nil, skill = nil, modex = nil, @@ -233,6 +237,17 @@ function SQUADRON:SetSkill(Skill) return self end +--- Set maintenance and repair time. +-- @param #SQUADRON self +-- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 0 min. +-- @param #number RepairTime Time in minutes it takes to repair a flight for each percent damage taken. Default is 0 min. +-- @return #SQUADRON self +function SQUADRON:SetMaintenanceTime(MaintenanceTime, RepairTime) + self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 + self.repairtime=RepairTime and RepairTime*60 or 0 + return self +end + --- Set radio frequency and modulation the squad uses. -- @param #SQUADRON self -- @param #number Frequency Radio frequency in MHz. Default 251 MHz. @@ -683,12 +698,12 @@ function SQUADRON:RecruitAssets(Mission) -- Asset is already on a mission. --- - -- Check if this asset is currently on a PATROL mission (STARTED or EXECUTING). - if self.airwing:IsAssetOnMission(asset, AUFTRAG.Type.PATROL) and Mission.type==AUFTRAG.Type.INTERCEPT then + -- Check if this asset is currently on a GCCAP mission (STARTED or EXECUTING). + if self.airwing:IsAssetOnMission(asset, AUFTRAG.Type.GCCAP) and Mission.type==AUFTRAG.Type.INTERCEPT then -- Check if the payload of this asset is compatible with the mission. - -- Note: we do not check the payload as an asset that is on a PATROL mission should be able to do an INTERCEPT as well! - self:I(self.lid.."Adding asset on PATROL mission for an INTERCEPT mission") + -- Note: we do not check the payload as an asset that is on a GCCAP mission should be able to do an INTERCEPT as well! + self:I(self.lid.."Adding asset on GCCAP mission for an INTERCEPT mission") table.insert(assets, asset) end @@ -696,7 +711,7 @@ function SQUADRON:RecruitAssets(Mission) else --- - -- Asset as no current mission + -- Asset as NO current mission --- if asset.spawned then @@ -744,7 +759,7 @@ function SQUADRON:RecruitAssets(Mission) --- -- Check that asset is not already requested for another mission. - if Npayloads>0 and not asset.requested then + if Npayloads>0 and self:IsRepaired(asset) and (not asset.requested) then -- Add this asset to the selection. table.insert(assets, asset) @@ -761,6 +776,26 @@ function SQUADRON:RecruitAssets(Mission) return assets end +--- Checks if a mission type is contained in a table of possible types. +-- @param #SQUADRON self +-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. +-- @return #boolean If true, the requested mission type is part of the possible mission types. +function SQUADRON:IsRepaired(Asset) + + if Asset.Treturned then + local Tnow=timer.getAbsTime() + if Asset.Treturned+self.maintenancetime>=Tnow then + return true + else + return false + end + + else + return true + end + +end + --- Checks if a mission type is contained in a table of possible types. -- @param #SQUADRON self diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 254f4d977..ffbaca4ab 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -16,11 +16,15 @@ -- @type TARGET -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #table targets Table of target objects. -- @field #number targetcounter Running number to generate target object IDs. -- @field #number life Total life points on last status update. -- @field #number life0 Total life points of completely healthy targets. +-- @field #number threatlevel0 Initial threat level. +-- @field #number category Target category (Ground, Air, Sea). +-- @field #number Ntargets0 Number of initial targets. -- @extends Core.Fsm#FSM --- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D. Eisenhower @@ -38,11 +42,14 @@ TARGET = { ClassName = "TARGET", Debug = nil, + verbose = 0, lid = nil, targets = {}, targetcounter = 0, life = 0, life0 = 0, + Ntargets0 = 0, + threatlevel0 = 0 } @@ -60,7 +67,23 @@ TARGET.ObjectType={ AIRBASE="Airbase", } ---- Type. + +--- Category. +-- @type TARGET.Category +-- @field #string AIRCRAFT +-- @field #string GROUND +-- @field #string NAVAL +-- @field #string AIRBASE +-- @field #string COORDINATE +TARGET.Category={ + AIRCRAFT="Aircraft", + GROUND="Grund", + NAVAL="Naval", + AIRBASE="Airbase", + COORDINATE="Coordinate", +} + +--- Object status. -- @type TARGET.ObjectStatus -- @field #string ALIVE Object is alive. -- @field #string DEAD Object is dead. @@ -105,10 +128,15 @@ function TARGET:New(TargetObject) -- Increase counter. _TARGETID=_TARGETID+1 - - self.lid=string.format("TARGET #%03d | ", _TARGETID) - + + -- Add object. self:AddObject(TargetObject) + + local Target=self.targets[1] --#TARGET.Object + + self.category=self:GetTargetCategory(Target) + + self.lid=string.format("TARGET #%03d %s | ", _TARGETID, self.category) -- Start state. self:SetStartState("Stopped") @@ -179,30 +207,23 @@ end -- @param #TARGET self -- @param Wrapper.Positionable#POSITIONABLE Object The target GROUP, UNIT, STATIC, AIRBASE or COORDINATE. function TARGET:AddObject(Object) + + if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") then + + --- + -- Sets + --- - if Object:IsInstanceOf("GROUP") then - - local group=Object --Wrapper.Group#GROUP - - local units=group:GetUnits() - - for _,unit in pairs(units) do - self:_AddObject(unit) - end - - elseif Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") then - local set=Object --Core.Set#SET_GROUP for _,object in pairs(set.Set) do self:AddObject(object) - end - + end else --- - -- Units, Statics, Airbases, Coordinates + -- Groups, Units, Statics, Airbases, Coordinates --- self:_AddObject(Object) @@ -265,7 +286,7 @@ function TARGET:onafterStatus(From, Event, To) end -- Log output. - local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), #self.targets, self:GetLife(), self:GetLife0(), self:GetDamage()) + local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.Ntargets0, self:GetLife(), self:GetLife0(), self:GetDamage()) if damaged then text=text.." Damaged!" end @@ -277,7 +298,7 @@ function TARGET:onafterStatus(From, Event, To) for i,_target in pairs(self.targets) do local target=_target --#TARGET.Object local damage=(1-target.Life/target.Life0)*100 - text=text..string.format("\n[%d] %s %s: Life=%.1f/%.1f, Damage=%.1f", i, target.Name, target.Status, target.Life, target.Life0, damage) + text=text..string.format("\n[%d] %s %s %s: Life=%.1f/%.1f, Damage=%.1f", i, target.Type, target.Name, target.Status, target.Life, target.Life0, damage) end self:I(self.lid..text) end @@ -397,7 +418,30 @@ function TARGET:_AddObject(Object) local target={} --#TARGET.Object - if Object:IsInstanceOf("UNIT") then + if Object:IsInstanceOf("GROUP") then + + local group=Object --Wrapper.Group#GROUP + + target.Type=TARGET.ObjectType.GROUP + target.Name=group:GetName() + + local units=group:GetUnits() + + target.Life=0 ; target.Life0=0 + for _,_unit in pairs(units or {}) do + local unit=_unit --Wrapper.Unit#UNIT + + local life=unit:GetLife() + + target.Life=target.Life+life + target.Life0=target.Life0+math.max(unit:GetLife0(), life) -- There was an issue with ships that life is greater life0, which cannot be! + + self.threatlevel0=self.threatlevel0+unit:GetThreatLevel() + + self.Ntargets0=self.Ntargets0+1 + end + + elseif Object:IsInstanceOf("UNIT") then local unit=Object --Wrapper.Unit#UNIT @@ -407,6 +451,10 @@ function TARGET:_AddObject(Object) if unit and unit:IsAlive() then target.Life=unit:GetLife() target.Life0=math.max(unit:GetLife0(), target.Life) -- There was an issue with ships that life is greater life0! + + self.threatlevel0=self.threatlevel0+unit:GetThreatLevel() + + self.Ntargets0=self.Ntargets0+1 end elseif Object:IsInstanceOf("STATIC") then @@ -418,7 +466,9 @@ function TARGET:_AddObject(Object) if static and static:IsAlive() then target.Life0=1 - target.Life=1 + target.Life=1 + + self.Ntargets0=self.Ntargets0+1 end @@ -430,8 +480,9 @@ function TARGET:_AddObject(Object) target.Name=airbase:GetName() target.Life0=1 - target.Life=1 - + target.Life=1 + + self.Ntargets0=self.Ntargets0+1 elseif Object:IsInstanceOf("COORDINATE") then @@ -442,6 +493,9 @@ function TARGET:_AddObject(Object) target.Life0=1 target.Life=1 + + -- TODO: does this make sense for a coordinate? + --self.Ntargets0=self.Ntargets0+1 else self:E(self.lid.."ERROR: Unknown object type!") @@ -488,7 +542,24 @@ end -- @return #number Life points of target. function TARGET:GetTargetLife(Target) - if Target.Type==TARGET.ObjectType.UNIT then + if Target.Type==TARGET.ObjectType.GROUP then + + if Target.Object and Target.Object:IsAlive() then + + local units=Target.Object:GetUnits() + + local life=0 + for _,_unit in pairs(units or {}) do + local unit=_unit --Wrapper.Unit#UNIT + life=life+unit:GetLife() + end + + return life + else + return 0 + end + + elseif Target.Type==TARGET.ObjectType.UNIT then if Target.Object and Target.Object:IsAlive() then return Target.Object:GetLife() @@ -526,19 +597,7 @@ end function TARGET:GetLife() local N=0 - - local function _GetLife(unit) - local unit=unit --Wrapper.Unit#UNIT - if Healthy then - local life=unit:GetLife() - local life0=unit:GetLife0() - - return math.max(life, life0) - else - return unit:GetLife() - end - end - + for _,_target in pairs(self.targets) do local Target=_target --#TARGET.Object @@ -549,6 +608,132 @@ function TARGET:GetLife() return N end + + +--- Get target coordinate. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return Core.Point#COORDINATE Coordinate of the target. +function TARGET:GetTargetCoordinate(Target) + + if Target.Type==TARGET.ObjectType.GROUP then + + if Target.Object and Target.Object:IsAlive() then + + return Target.Object:GetCoordinate() + + end + + elseif Target.Type==TARGET.ObjectType.UNIT then + + if Target.Object and Target.Object:IsAlive() then + return Target.Object:GetCoordinate() + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + if Target.Object and Target.Object:IsAlive() then + return Target.Object:GetCoordinate() + end + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + if Target.Status==TARGET.ObjectStatus.ALIVE then + return Target.Object:GetCoordinate() + end + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + return Target.Object + + end + + return nil +end + +--- Get coordinate. +-- @param #TARGET self +-- @return Core.Point#COORDINATE Coordinate of the target. +function TARGET:GetCoordinate() + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + local coordinate=self:GetTargetCoordinate(Target) + + if coordinate then + return coordinate + end + + end + + return nil +end + + +--- Get target category +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return #TARGET.Category Target category. +function TARGET:GetTargetCategory(Target) + + local category=nil + + if Target.Type==TARGET.ObjectType.GROUP then + + if Target.Object and Target.Object:IsAlive()~=nil then + + local group=Target.Object --Wrapper.Group#GROUP + + local cat=group:GetCategory() + + if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then + category=TARGET.Category.AIRCRAFT + elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then + category=TARGET.Category.GROUND + elseif cat==Group.Category.SHIP then + category=TARGET.Category.NAVAL + end + + end + + elseif Target.Type==TARGET.ObjectType.UNIT then + + if Target.Object and Target.Object:IsAlive() then + local unit=Target.Object --Wrapper.Unit#UNIT + + local group=unit:GetGroup() + + local cat=group:GetCategory() + + if cat==Group.Category.AIRPLANE or cat==Group.Category.HELICOPTER then + category=TARGET.Category.AIRCRAFT + elseif cat==Group.Category.GROUND or cat==Group.Category.TRAIN then + category=TARGET.Category.GROUND + elseif cat==Group.Category.SHIP then + category=TARGET.Category.NAVAL + end + + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + return TARGET.Category.GROUND + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + return TARGET.Category.AIRBASE + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + return TARGET.Category.COORDINATE + + end + + return category +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -580,7 +765,20 @@ function TARGET:CountTargets() for _,_target in pairs(self.targets) do local Target=_target --#TARGET.Object - if Target.Type==TARGET.ObjectType.UNIT then + if Target.Type==TARGET.ObjectType.GROUP then + + local target=Target.Object --Wrapper.Group#GROUP + + local units=target:GetUnits() + + for _,_unit in pairs(units or {}) do + local unit=_unit --Wrapper.Unit#UNIT + if unit and unit:IsAlive() and unit:GetLife()>1 then + N=N+1 + end + end + + elseif Target.Type==TARGET.ObjectType.UNIT then local target=Target.Object --Wrapper.Unit#UNIT diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 36a7ca95e..ccd19ae60 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2213,6 +2213,45 @@ function GROUP:GetDCSDesc(n) return nil end +--- Get health of the group. +-- @param #GROUP self +-- @return #number Health in percent. +function GROUP:GetHealth() + + local lp=0 + local lp0=0 + + local units=self:GetUnits() + + for _,_unit in pairs(units or {}) do + local unit=_unit --Wrapper.Unit#UNIT + + if unit and unit:IsAlive() then + local life=unit:GetLife() + local life0=unit:GetLife0() + life0=math.max(life0, life) --Issue with ships + + lp=lp+life + lp0=lp0+life + + end + + end + + if lp0>0 then + return lp/lp0*100 + else + return 0 + end +end + +--- Get damage of the group. +-- @param #GROUP self +-- @return #number Damage in percent. +function GROUP:GetDamage() + return 100-self:GetHealth() +end + --- Get the generalized attribute of a self. -- Note that for a heterogenious self, the attribute is determined from the attribute of the first unit! From c7696c375ee970cb9f2bbd81138d8ed41a93c566 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 Aug 2020 01:46:03 +0200 Subject: [PATCH 31/79] Ops --- .../Moose/Functional/Warehouse.lua | 31 ++--- Moose Development/Moose/Ops/AirWing.lua | 121 ++++++++++++------ Moose Development/Moose/Ops/ArmyGroup.lua | 3 + Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 45 +++++-- Moose Development/Moose/Ops/OpsGroup.lua | 6 +- .../Moose/Ops/RecoveryTanker.lua | 2 - Moose Development/Moose/Ops/Squadron.lua | 117 ++++++++++++++++- Moose Development/Moose/Ops/Target.lua | 33 +++-- .../Moose/Utilities/Profiler.lua | 6 +- .../Moose/Wrapper/Controllable.lua | 4 +- 11 files changed, 272 insertions(+), 98 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 98bce2200..213805a8d 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3825,7 +3825,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Destroy group if it is alive. if group:IsAlive()==true then - asset.damage=group:GetDamage() + asset.damage=asset.life0-group:GetLife() end -- Add asset to stock. @@ -3871,6 +3871,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu if group:IsAlive()==true then self:_DebugMessage(string.format("Removing group %s", group:GetName()), 5) -- Setting parameter to false, i.e. creating NO dead or remove unit event, seems to not confuse the dispatcher logic. + -- TODO: It would be nice, however, to have the remove event. group:Destroy(false) end @@ -3997,6 +3998,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, asset.skill=skill asset.assignment=assignment asset.spawned=false + asset.life0=group:GetLife0() asset.damage=0 asset.spawngroupname=string.format("%s_AID-%d", templategroupname, asset.uid) @@ -4797,22 +4799,8 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) group:RouteGroundTo(warehouse:GetCoordinate(), group:GetSpeedMax()*0.3, "Off Road") end - -- NOTE: This is done in the AddAsset() function. Dont know, why we do it also here. - --[[ - if istransport==true then - request.ntransporthome=request.ntransporthome+1 - request.transportgroupset:Remove(group:GetName(), true) - self:T2(warehouse.lid..string.format("Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) - elseif istransport==false then - request.ndelivered=request.ndelivered+1 - request.cargogroupset:Remove(self:_GetNameWithOut(group), true) - self:T2(warehouse.lid..string.format("Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) - else - self:E(warehouse.lid..string.format("ERROR: Group %s is neither cargo nor transport!", group:GetName())) - end - ]] - -- Move asset from pending queue into new warehouse. + env.info("FF asset arrived in wh. adding in 60 sec") warehouse:__AddAsset(60, group) end @@ -6267,7 +6255,6 @@ function WAREHOUSE:_OnEventArrived(EventData) local istransport=self:_GroupIsTransport(group, request) -- Get closest airbase. - -- Note, this crashed at somepoint when the Tarawa was in the mission. Don't know why. Deleting the Tarawa and adding it again solved the problem. local closest=group:GetCoordinate():GetClosestAirbase() -- Check if engine shutdown happend at right airbase because the event is also triggered in other situations. @@ -6276,15 +6263,17 @@ function WAREHOUSE:_OnEventArrived(EventData) -- Check that group is cargo and not transport. if istransport==false and rightairbase then - -- Debug info. - local text=string.format("Air asset group %s from warehouse %s arrived at its destination.", group:GetName(), self.alias) - self:_InfoMessage(text) - -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since -- some units might still be taxiing or whatever. Therefore, we add 10 seconds for each additional unit of the group until the first arrived event is triggered. local nunits=#group:GetUnits() local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. + + -- Debug info. + local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec", group:GetName(), self.alias, dt) + self:_InfoMessage(text) + + self:__Arrived(dt, group) end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 2d2d28ae7..0138f78b4 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -193,9 +193,13 @@ function AIRWING:New(warehousename, airwingname) self.lid=string.format("AIRWING %s | ", self.alias) -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. - self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + -- From State --> Event --> To State + self:AddTransition("*", "MissionRequest", "*") -- Add a (mission) request to the warehouse. + self:AddTransition("*", "MissionCancel", "*") -- Cancel mission. + + self:AddTransition("*", "SquadAssetReturned", "*") -- Flight was spawned with a mission. + + self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. -- Defaults: self.nflightsCAP=0 @@ -286,14 +290,9 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) if type(Unit)=="string" then local name=Unit - env.info("unit as string "..Unit) Unit=UNIT:FindByName(name) if not Unit then - env.info("no UNIT trying group") Unit=GROUP:FindByName(name) - if not Unit then - env.info("no GROUP either!") - end end end @@ -808,7 +807,7 @@ function AIRWING:onafterStatus(From, Event, To) text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) -- Loop over all assets. - if self.verbose>0 then + if self.verbose>1 then for j,_asset in pairs(squadron.assets) do local asset=_asset --#AIRWING.SquadronAsset local assignment=asset.assignment or "none" @@ -1062,7 +1061,11 @@ function AIRWING:GetTankerForFlight(flightgroup) local dist=assetcoord:Get2DDistance(tankercoord) - table.insert(tankeropt, {tanker=tanker, dist=dist}) + -- Ensure that the flight does not find itself. Asset could be a tanker! + if dist>5 then + table.insert(tankeropt, {tanker=tanker, dist=dist}) + end + end end @@ -1070,7 +1073,11 @@ function AIRWING:GetTankerForFlight(flightgroup) table.sort(tankeropt, function(a,b) return a.dist0 then + return tankeropt[1].tanker + else + return nil + end end return nil @@ -1218,7 +1225,9 @@ function AIRWING:CalculateAssetMissionScore(asset, Mission, includePayload) local score=0 -- Prefer highly skilled assets. - if asset.skill==AI.Skill.GOOD then + if asset.skill==AI.Skill.AVERAGE then + score=score+0 + elseif asset.skill==AI.Skill.GOOD then score=score+10 elseif asset.skill==AI.Skill.HIGH then score=score+20 @@ -1337,7 +1346,7 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) -- Need to dived to set into spawned and instock assets and handle the other --- - -- Assets to be requested + -- Assets to be requested. local Assetlist={} for _,_asset in pairs(Mission.assets) do @@ -1350,6 +1359,9 @@ function AIRWING:onafterMissionRequest(From, Event, To, Mission) -- Add new mission. asset.flightgroup:AddMission(Mission) + -- Trigger event. + self:FlightOnMission(asset.flightgroup, Mission) + else self:E(self.lid.."ERROR: flight group for asset does NOT exist!") end @@ -1438,7 +1450,7 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Debug text. local text=string.format("New asset %s with assignment %s and request assignment %s", asset.spawngroupname, tostring(asset.assignment), tostring(assignment)) - self:T3(self.lid..text) + self:I(self.lid..text) -- Get squadron. local squad=self:GetSquadron(asset.assignment) @@ -1454,10 +1466,7 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) self:I(self.lid..text) - -- Create callsign and modex. - squad:GetCallsign(asset) - squad:GetModex(asset) - + -- Adjust number of elements in the group. if squad.ngrouping then local template=asset.template @@ -1471,7 +1480,7 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- If grouping is larger than units present, copy first unit. if i>nunits then - table.insert(template.units, UTILS.DeepCopy(template.units[1])) + table.insert(template.units, UTILS.DeepCopy(template.units[1])) end -- Remove units if original template contains more than in grouping. @@ -1483,24 +1492,48 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) asset.nunits=squad.ngrouping end + -- Create callsign and modex (needs to be after grouping). + squad:GetCallsign(asset) + squad:GetModex(asset) + + -- Set spawn group name. This has to include "AID-" for warehouse. + asset.spawngroupname=string.format("%s_AID-%d", squad.name, asset.uid) + -- Add asset to squadron. squad:AddAsset(asset) - --asset.terminalType=AIRBASE.TerminalType.OpenBig + -- TODO + --asset.terminalType=AIRBASE.TerminalType.OpenBig else - self:I(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\", assignment=\"%s\"", asset.spawngroupname, squad.name, tostring(asset.assignment), tostring(assignment))) - self:ReturnPayloadFromAsset(asset) - - -- Set timestamp. - asset.Treturned=timer.getAbsTime() + env.info("FF squad asset returned") + self:SquadAssetReturned(squad, asset) end end end - +--- On after "AssetReturned" event. Triggered when an asset group returned to its airwing. +-- @param #AIRWING self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.Squadron#SQUADRON Squadron The asset squadron. +-- @param #AIRWING.SquadronAsset Asset The asset that returned. +function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) + -- Debug message. + self:I(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) + + -- Stop flightgroup. + Asset.flightgroup:Stop() + + -- Return payload. + self:ReturnPayloadFromAsset(Asset) + + -- Set timestamp. + Asset.Treturned=timer.getAbsTime() +end --- On after "AssetSpawned" event triggered when an asset group is spawned into the cruel world. @@ -1519,10 +1552,7 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Create a flight group. local flightgroup=self:_CreateFlightGroup(asset) - - -- Set RTB on fuel critical. - flightgroup:SetFuelCriticalThreshold(nil, true) - + -- Set airwing. flightgroup:SetAirwing(self) @@ -1547,6 +1577,12 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Not requested any more. asset.requested=nil + -- Did not return yet. + asset.Treturned=nil + + -- Set RTB on fuel critical. + flightgroup:SetFuelCriticalThreshold() + -- Get Mission (if any). local mission=self:GetMissionByID(request.assignment) @@ -1555,11 +1591,15 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- RTB on low fuel if on GCCAP. if mission.type==AUFTRAG.Type.GCCAP then - flightgroup:SetFuelLowThreshold(nil, true) + flightgroup:SetFuelLowThreshold(25) + flightgroup:SetFuelLowRTB(true) end -- Add mission to flightgroup queue. asset.flightgroup:AddMission(mission) + + -- Trigger event. + self:FlightOnMission(flightgroup, mission) end -- Add group to the detection set of the WINGCOMMANDER. @@ -1869,10 +1909,11 @@ end --- Count assets on mission. -- @param #AIRWING self -- @param #table MissionTypes Types on mission to be checked. Default all. +-- @param Ops.Squadron#SQUADRON Squadron Only count assets of this squadron. Default count assets of all squadrons. -- @return #number Number of pending and queued assets. -- @return #number Number of pending assets. -- @return #number Number of queued assets. -function AIRWING:CountAssetsOnMission(MissionTypes) +function AIRWING:CountAssetsOnMission(MissionTypes, Squadron) local Nq=0 local Np=0 @@ -1881,17 +1922,21 @@ function AIRWING:CountAssetsOnMission(MissionTypes) local mission=_mission --Ops.Auftrag#AUFTRAG -- Check if this mission type is requested. - if self:CheckMissionType(mission.type, MissionTypes) then + if self:CheckMissionType(mission.type, MissionTypes or AUFTRAG.Type) then for _,_asset in pairs(mission.assets or {}) do local asset=_asset --#AIRWING.SquadronAsset - local request, isqueued=self:GetRequestByID(mission.requestID) + if Squadron==nil or Squadron.name==asset.squadname then - if isqueued then - Nq=Nq+1 - else - Np=Np+1 + local request, isqueued=self:GetRequestByID(mission.requestID) + + if isqueued then + Nq=Nq+1 + else + Np=Np+1 + end + end end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index e22ba147e..0c6d1577d 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -703,6 +703,9 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Add waypoint to table. self:_AddWaypoint(waypoint, wpnumber) + -- Get closest point to road. + waypoint.roadcoord=Coordinate:GetClosestPointToRoad(false) + -- Debug info. self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index cb7944935..4c02e8cb7 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -659,7 +659,7 @@ end -- @param #number Speed Orbit speed in knots. Default 350 kts. -- @param #number Heading Heading of race-track pattern in degrees. Default 270 (East to West). -- @param #number Leg Length of race-track in NM. Default 10 NM. --- @param #number RefuelSystem Refueling system. +-- @param #number RefuelSystem Refueling system (0=boom, 1=probe). This info is *only* for AIRWINGs so they launch the right tanker type. -- @return #AUFTRAG self function AUFTRAG:NewTANKER(Coordinate, Altitude, Speed, Heading, Leg, RefuelSystem) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 34aaaa818..b4b3e0ce9 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -242,8 +242,8 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet! self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern. - self:AddTransition("*", "Refuel", "Going4Fuel") -- Group is send to refuel at a tanker. Not implemented yet! - self:AddTransition("Going4Fuel", "Refueled", "Airborne") -- Group is send to refuel at a tanker. Not implemented yet! + self:AddTransition("*", "Refuel", "Going4Fuel") -- Group is send to refuel at a tanker. + self:AddTransition("Going4Fuel", "Refueled", "Airborne") -- Group finished refueling. self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. @@ -449,11 +449,22 @@ end --- Set fuel critical threshold. Triggers event "FuelCritical" and event function "OnAfterFuelCritical". -- @param #FLIGHTGROUP self -- @param #number threshold Fuel threshold in percent. Default 10 %. --- @param #boolean rtb If true, RTB on fuel critical event. -- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetFuelCriticalThreshold(threshold, rtb) +function FLIGHTGROUP:SetFuelCriticalThreshold(threshold) self.fuelcriticalthresh=threshold or 10 - self.fuelcriticalrtb=rtb + return self +end + +--- Set if critical fuel threshold is reached, flight goes RTB. +-- @param #FLIGHTGROUP self +-- @param #boolean switch If true or nil, flight goes RTB. If false, turn this off. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetFuelCriticalRTB(switch) + if switch==false then + self.fuelcriticalrtb=false + else + self.fuelcriticalrtb=true + end return self end @@ -1593,7 +1604,9 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) end -- Stop and despawn in 5 min. - self:__Stop(5*60) + if not self.airwing then + self:__Stop(5*60) + end end --- On after "Dead" event. @@ -1930,6 +1943,12 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Clear holding time in any case. self.Tholding=nil + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + self:MissionCancel(mission) + end -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) @@ -2326,9 +2345,14 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) local tanker=self.airwing:GetTankerForFlight(self) if tanker then + + self:I(self.lid..string.format("Send to refuel at tanker %s", tanker.flightgroup:GetName())) + + -- Get a coordinate towards the tanker. + local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker.flightgroup:GetCoordinate(), 0.75) -- Send flight to tanker with refueling task. - self:Refuel(tanker.flightgroup:GetCoordinate()) + self:Refuel(coordinate) else @@ -2349,7 +2373,10 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) self:I(self.lid..string.format("Send to refuel at tanker %s", tanker:GetName())) - self:Refuel() + -- Get a coordinate towards the tanker. + local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker.flightgroup:GetCoordinate(), 0.75) + + self:Refuel(coordinate) return end @@ -2736,7 +2763,7 @@ end --- Find the nearest tanker. -- @param #FLIGHTGROUP self -- @param #number Radius Search radius in NM. Default 50 NM. --- @return Wrapper.Group#GROUP Closest tanker group #nil. +-- @return Wrapper.Group#GROUP Closest tanker group or `nil` if no tanker is in the given radius. function FLIGHTGROUP:FindNearestTanker(Radius) Radius=UTILS.NMToMeters(Radius or 50) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 0ce61fdc7..14b8db4e8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -245,7 +245,7 @@ OPSGROUP.TaskType={ -- @field #number speed Speed in m/s. -- @field #number alt Altitude in meters. For submaries use negative sign for depth. -- @field #string action Waypoint action (turning point, etc.). Ground groups have the formation here. --- @field #table task Waypoint task combo. +-- @field #table task Waypoint DCS task combo. -- @field #string type Waypoint type. -- @field #string name Waypoint description. Shown in the F10 map. -- @field #number x Waypoint x-coordinate. @@ -254,6 +254,7 @@ OPSGROUP.TaskType={ -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. +-- @field Core.Point#COORDINATE roadcoord Closest point to road. -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. --- NavyGroup version. @@ -417,8 +418,9 @@ function OPSGROUP:GetSpeedCruise() end --- Set detection on or off. +-- If detection is on, detected targets of the group will be evaluated and FSM events triggered. -- @param #OPSGROUP self --- @param #boolean Switch If true, detection is on. If false or nil, detection is off. Default is off. +-- @param #boolean Switch If `true`, detection is on. If `false` or `nil`, detection is off. Default is off. -- @return #OPSGROUP self function OPSGROUP:SetDetection(Switch) self.detectionOn=Switch diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index e43cd9cc7..c368c7418 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -1345,7 +1345,6 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) group:InitModex(self.modex) -- Respawn tanker. Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - --SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1) self:ScheduleOnce(1, GROUP.RespawnAtCurrentAirbase, group) -- Create tanker beacon and activate TACAN. @@ -1364,7 +1363,6 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) end -- Initial route. - --SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2) self:ScheduleOnce(2, RECOVERYTANKER._InitRoute, self, -self.distStern+UTILS.NMToMeters(3)) end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 88261aa7c..0c1939570 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -18,6 +18,7 @@ -- @type SQUADRON -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #string name Name of the squadron. -- @field #string templatename Name of the template group. @@ -63,6 +64,7 @@ SQUADRON = { ClassName = "SQUADRON", Debug = nil, + verbose = 0, lid = nil, name = nil, templatename = nil, @@ -563,14 +565,23 @@ function SQUADRON:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() + + local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A" + local modex=self.modex and self.modex or -1 + local skill=self.skill and tostring(self.skill) or "N/A" - -- Check if group has detected any units. - --self:_CheckAssetStatus() + local NassetsTot=#self.assets + local NassetsInS=self:CountAssetsInStock() + local NassetsQP, NassetsP, NassetsQ=self.airwing and self.airwing:CountAssetsOnMission(nil, self) or 0,0,0 -- Short info. - local text=string.format("Status %s: Assets %d", fsmstate, #self.assets) + local text=string.format("%s [Type=%s, Callsign=%s, Modex=%d, Skill=%s]: Assets Total=%d, InStock=%d, OnMission=%d [P=%d, Q=%d]", + fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ) self:I(self.lid..text) + -- Check if group has detected any units. + self:_CheckAssetStatus() + if not self:IsStopped() then self:__Status(-30) end @@ -581,9 +592,74 @@ end -- @param #SQUADRON self function SQUADRON:_CheckAssetStatus() - for _,_asset in pairs(self.assets) do - local asset=_asset - + if self.verbose>=0 then + local text="" + for j,_asset in pairs(self.assets) do + local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset + + -- Text. + text=text..string.format("\n-[%d] %s*%d: ", j, asset.unittype, asset.nunits) + + if asset.spawned then + + --- + -- Spawned + --- + + -- Mission info. + local mission=self.airwing and self.airwing:GetAssetCurrentMission(asset) or false + if mission then + local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 + text=text..string.format(" Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) + end + + -- Flight status. + text=text..", Flight: " + if asset.flightgroup and asset.flightgroup:IsAlive() then + local status=asset.flightgroup:GetState() + local fuelmin=asset.flightgroup:GetFuelMin() + local fuellow=asset.flightgroup:IsFuelLow() + local fuelcri=asset.flightgroup:IsFuelCritical() + + text=text..string.format("%s Fuel=%d", status, fuelmin) + if fuelcri then + text=text.." (Critical!)" + elseif fuellow then + text=text.." (Low)" + end + + local lifept, lifept0=asset.flightgroup:GetLifePoints() + text=text..string.format(", Life=%d/%d", lifept, lifept0) + + local ammo=asset.flightgroup:GetAmmoTot() + text=text..string.format(", Ammo=%d [G=%d, R=%d, B=%d, M=%d]", ammo.Total,ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) + else + text=text.."N/A" + end + + -- Payload info. + local payload=asset.payload and table.concat(self.airwing:GetPayloadMissionTypes(asset.payload), ", ") or "None" + text=text..", Payload={"..payload.."}" + + + else + + --- + -- In Stock + --- + + if asset.Treturned then + local T=timer.getAbsTime()-asset.Treturned + text=text..string.format(" Treturn=%d sec", T) + end + if asset.damage then + text=text..string.format(" Damage=%.1f", asset.damage) + end + text=text..string.format(" Repaired=%s T=%d sec", tostring(self:IsRepaired(asset)), self:GetRepairTime(asset)) + + end + end + self:I(self.lid..text) end end @@ -603,6 +679,8 @@ function SQUADRON:onafterStop(From, Event, To) self:DelAsset(asset) end + self.CallScheduler:Clear() + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -776,6 +854,30 @@ function SQUADRON:RecruitAssets(Mission) return assets end + +--- Get the time an asset needs to be repaired. +-- @param #SQUADRON self +-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. +-- @return #number Time in seconds until asset is repaired. +function SQUADRON:GetRepairTime(Asset) + + if Asset.Treturned then + + local t=self.maintenancetime + t=t+Asset.damage*self.repairtime + + -- Seconds after returned. + local dt=timer.getAbsTime()-Asset.Treturned + + local T=t-dt + + return T + else + return 0 + end + +end + --- Checks if a mission type is contained in a table of possible types. -- @param #SQUADRON self -- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. @@ -784,7 +886,8 @@ function SQUADRON:IsRepaired(Asset) if Asset.Treturned then local Tnow=timer.getAbsTime() - if Asset.Treturned+self.maintenancetime>=Tnow then + local Trepaired=Asset.Treturned+self.maintenancetime + if Tnow>=Trepaired then return true else return false diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index ffbaca4ab..d2e9acc88 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -2,8 +2,9 @@ -- -- **Main Features:** -- --- * Manages AIRWINGS --- * Events when units get +-- * Manages target, number alive, life points, damage etc. +-- * Events when targets are damaged or destroyed +-- * Various target objects: UNIT, GROUP, STATIC, SET_UNIT, AIRBASE, COORDINATE, SET_GROUP, SET_UNIT -- -- === -- @@ -35,7 +36,9 @@ -- -- # The TARGET Concept -- --- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned TARGET (mission). +-- Define a target of your mission and monitor its status. Events are triggered when the target is damaged or destroyed. +-- +-- A target can consist of one or multiple "objects". -- -- -- @field #TARGET @@ -247,7 +250,7 @@ function TARGET:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting Target") - self:I(self.lid..text) + self:T(self.lid..text) self:HandleEvent(EVENTS.Dead, self.OnEventUnitDeadOrLost) self:HandleEvent(EVENTS.UnitLost, self.OnEventUnitDeadOrLost) @@ -272,12 +275,16 @@ function TARGET:onafterStatus(From, Event, To) local damaged=false for i,_target in pairs(self.targets) do local target=_target --#TARGET.Object + local life=target.Life + target.Life=self:GetTargetLife(target) + if target.Life=1 then + local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.Ntargets0, self:GetLife(), self:GetLife0(), self:GetDamage()) + if damaged then + text=text.." Damaged!" + end + self:I(self.lid..text) + end - -- Verbose output. - if true then + -- Log output verbose=2. + if self.verbose>=2 then local text="Target:" for i,_target in pairs(self.targets) do local target=_target --#TARGET.Object diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index c61a079da..7dabbb068 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -82,8 +82,8 @@ PROFILER = { } PROFILER.sortBy=1 -- Sort reports by 0=Count, 1=Total time by function -PROFILER.logUnknown=false -- Log unknown functions -PROFILER.lowCpsThres=5 -- Skip results with less than X calls per second +PROFILER.logUnknown=true -- Log unknown functions +PROFILER.lowCpsThres=1 -- Skip results with less than X calls per second PROFILER.fileName="_LuaProfiler.txt" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -252,7 +252,7 @@ function PROFILER.showInfo() local file=lfs.writedir()..[[Logs\]]..PROFILER.fileName local f=io.open(file, 'w') - BASE:I(string.format("### Profiler: Writing result to file ", file)) + BASE:I(string.format("### Profiler: Writing result to file %s", file)) -- Gather data. local t={} diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 322df4137..9dded0f5a 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -243,8 +243,7 @@ end --- Returns the initial health. -- @param #CONTROLLABLE self --- @return #number The controllable health value (unit or group average). --- @return #nil The controllable is not existing or alive. +-- @return #number The controllable health value (unit or group average) or `nil` if the controllable does not exist. function CONTROLLABLE:GetLife0() self:F2( self.ControllableName ) @@ -296,7 +295,6 @@ end -- @return #nil The CONTROLLABLE is not existing or alive. function CONTROLLABLE:GetFuel() self:F( self.ControllableName ) - return nil end From fb6ebc42a5243fb868a54e317971f11d4c036561 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 10 Aug 2020 23:45:52 +0200 Subject: [PATCH 32/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 12 + Moose Development/Moose/Ops/Auftrag.lua | 270 ++++++++++++++------ Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 5 - 4 files changed, 202 insertions(+), 87 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 0c6d1577d..3ced05e52 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -428,6 +428,17 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) end end + + + if formation==ENUMS.Formation.Vehicle.OnRoad then + + local wpnext=self:GetWaypointNext() + + if wpnext.action~=ENUMS.Formation.Vehicle.OnRoad then + + end + + end -- Debug info. self:I(string.format("WP %d %s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.type, wp.speed, wp.alt, wp.action)) @@ -705,6 +716,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Get closest point to road. waypoint.roadcoord=Coordinate:GetClosestPointToRoad(false) + waypoint.roadist=Coordinate:Get2DDistance(waypoint.roadcoord) -- Debug info. self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 4c02e8cb7..d22d07960 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -21,6 +21,7 @@ -- @type AUFTRAG -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #number auftragsnummer Auftragsnummer. -- @field #string type Mission type. @@ -255,6 +256,7 @@ AUFTRAG = { ClassName = "AUFTRAG", Debug = false, + verbose = 2, lid = nil, auftragsnummer = nil, groupdata = {}, @@ -627,7 +629,8 @@ function AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) return mission end ---- Create a GCCAP mission. +--- Create a Ground Controlled CAP (GCCAP) mission. Flights with this task are considered for A2A INTERCEPT missions by the CHIEF class. They will perform a compat air patrol but not engage by +-- themselfs. They wait for the CHIEF to tell them whom to engage. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. -- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. @@ -1178,6 +1181,96 @@ function AUFTRAG:NewTARGET(Target) end + +--- Create a mission to attack a group. Mission type is automatically chosen from the group category. +-- @param #AUFTRAG self +-- @param Wrapper.Positionable#POSITIONABLE Target Target object. +-- @return #string Auftrag type, e.g. `AUFTRAG.Type.BAI` (="BAI"). +function AUFTRAG:_DetermineAuftragType(Target) + + local group=nil --Wrapper.Group#GROUP + local airbase=nil --Wrapper.Airbase#AIRBASE + local scenery=nil --Wrapper.Scenery#SCENERY + local coordinate=nil --Core.Point#COORDINATE + local auftrag=nil + + if Target:IsInstanceOf("GROUP") then + group=Target --Target is already a group. + elseif Target:IsInstanceOf("UNIT") then + group=Target:GetGroup() + elseif Target:IsInstanceOf("AIRBASE") then + airbase=Target + elseif Target:IsInstanceOf("SCENERY") then + scenery=Target + end + + if group then + + local category=group:GetCategory() + local attribute=group:GetAttribute() + + if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then + + --- + -- A2A: Intercept + --- + + auftrag=AUFTRAG.Type.INTERCEPT + + elseif category==Group.Category.GROUND or category==Group.Category.TRAIN then + + --- + -- GROUND + --- + + if attribute==GROUP.Attribute.GROUND_SAM then + + -- SEAD/DEAD + + auftrag=AUFTRAG.Type.SEAD + + elseif attribute==GROUP.Attribute.GROUND_AAA then + + auftrag=AUFTRAG.Type.BAI + + elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then + + auftrag=AUFTRAG.Type.BAI + + elseif attribute==GROUP.Attribute.GROUND_INFANTRY then + + auftrag=AUFTRAG.Type.BAI + + else + + auftrag=AUFTRAG.Type.BAI + + end + + + elseif category==Group.Category.SHIP then + + --- + -- NAVAL + --- + + auftrag=AUFTRAG.Type.ANTISHIP + + else + self:E(self.lid.."ERROR: Unknown Group category!") + end + + elseif airbase then + auftrag=AUFTRAG.Type.BOMBRUNWAY + elseif scenery then + auftrag=AUFTRAG.Type.STRIKE + elseif coordinate then + auftrag=AUFTRAG.Type.BOMBING + end + + return auftrag +end + --- Create a mission to attack a group. Mission type is automatically chosen from the group category. -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP EngageGroup Group to be engaged. @@ -1186,66 +1279,54 @@ function AUFTRAG:NewAUTO(EngageGroup) local mission=nil --#AUFTRAG - local group=EngageGroup + local Target=EngageGroup - if group and group:IsAlive() then + local auftrag=self:_DetermineAuftragType(EngageGroup) - local category=group:GetCategory() - local attribute=group:GetAttribute() - local threatlevel=group:GetThreatLevel() + if auftrag==AUFTRAG.Type.ANTISHIP then + mission=AUFTRAG:NewANTISHIP(Target) + elseif auftrag==AUFTRAG.Type.ARTY then + mission=AUFTRAG:NewARTY(Target) + elseif auftrag==AUFTRAG.Type.AWACS then + mission=AUFTRAG:NewAWACS(Coordinate,Altitude,Speed,Heading,Leg) + elseif auftrag==AUFTRAG.Type.BAI then + mission=AUFTRAG:NewBAI(Target,Altitude) + elseif auftrag==AUFTRAG.Type.BOMBING then + mission=AUFTRAG:NewBOMBING(Target,Altitude) + elseif auftrag==AUFTRAG.Type.BOMBRUNWAY then + mission=AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude) + elseif auftrag==AUFTRAG.Type.BOMBCARPET then + mission=AUFTRAG:NewBOMBCARPET(Target,Altitude,CarpetLength) + elseif auftrag==AUFTRAG.Type.CAP then + mission=AUFTRAG:NewCAP(ZoneCAP,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) + elseif auftrag==AUFTRAG.Type.CAS then + mission=AUFTRAG:NewCAS(ZoneCAS,Altitude,Speed,Coordinate,Heading,Leg,TargetTypes) + elseif auftrag==AUFTRAG.Type.ESCORT then + mission=AUFTRAG:NewESCORT(EscortGroup,OffsetVector,EngageMaxDistance,TargetTypes) + elseif auftrag==AUFTRAG.Type.FACA then + mission=AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation) + elseif auftrag==AUFTRAG.Type.FERRY then + -- Not implemented yet. + elseif auftrag==AUFTRAG.Type.GCCAP then + mission=AUFTRAG:NewGCCAP(Coordinate,Altitude,Speed,Heading,Leg) + elseif auftrag==AUFTRAG.Type.INTERCEPT then + mission=AUFTRAG:NewINTERCEPT(Target) + elseif auftrag==AUFTRAG.Type.ORBIT then + mission=AUFTRAG:NewORBIT(Coordinate,Altitude,Speed,Heading,Leg) + elseif auftrag==AUFTRAG.Type.RECON then + -- Not implemented yet. + elseif auftrag==AUFTRAG.Type.RESCUEHELO then + mission=AUFTRAG:NewRESCUEHELO(Carrier) + elseif auftrag==AUFTRAG.Type.SEAD then + mission=AUFTRAG:NewSEAD(Target,Altitude) + elseif auftrag==AUFTRAG.Type.STRIKE then + mission=AUFTRAG:NewSTRIKE(Target,Altitude) + elseif auftrag==AUFTRAG.Type.TANKER then + mission=AUFTRAG:NewTANKER(Coordinate,Altitude,Speed,Heading,Leg,RefuelSystem) + elseif auftrag==AUFTRAG.Type.TROOPTRANSPORT then + mission=AUFTRAG:NewTROOPTRANSPORT(TransportGroupSet,DropoffCoordinate,PickupCoordinate) + else - if category==Group.Category.AIRPLANE or category==Group.Category.HELICOPTER then - - --- - -- AIR - --- - - mission=AUFTRAG:NewINTERCEPT(group) - - elseif category==Group.Category.GROUND then - - --- - -- GROUND - --- - - --TODO: action depends on type - -- AA/SAM ==> SEAD - -- Tanks ==> - -- Artillery ==> - -- Infantry ==> - -- - - if attribute==GROUP.Attribute.GROUND_AAA or attribute==GROUP.Attribute.GROUND_SAM then - - -- SEAD/DEAD - - -- TODO: Attack radars first? Attack launchers? - - mission=AUFTRAG:NewSEAD(group) - - elseif attribute==GROUP.Attribute.GROUND_ARTILLERY then - - mission=AUFTRAG:NewBAI(group) - - elseif attribute==GROUP.Attribute.GROUND_INFANTRY then - - mission=AUFTRAG:NewBAI(group) - - else - - mission=AUFTRAG:NewBAI(group) - - end - - elseif category==Group.Category.SHIP then - - --- - -- NAVAL - --- - - mission=AUFTRAG:NewANTISHIP(group) - - end end if mission then @@ -1255,7 +1336,19 @@ function AUFTRAG:NewAUTO(EngageGroup) return mission end +--- Create a mission to attack a group. Mission type is automatically chosen from the group category. +-- @param #AUFTRAG self +-- @param Ops.Target#TARGET Target Target to engage. +-- @return #AUFTRAG self +function AUFTRAG:NewTARGET(Target) + for _,_target in pairs(Target) do + local target=_target --Ops.Target#TARGET.Object + a=target.Object + end + + return mission +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- User API Functions @@ -1414,6 +1507,15 @@ function AUFTRAG:SetMissionAltitude(Altitude) return self end +--- Set mission speed. That is the speed the group uses to get to the mission waypoint. +-- @param #AUFTRAG self +-- @param #string Speed Mission speed in knots. +-- @return #AUFTRAG self +function AUFTRAG:SetMissionSpeed(Speed) + self.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil + return self +end + --- Set max engage range. -- @param #AUFTRAG self -- @param #number Range Max range in NM. Default 100 NM. @@ -1823,6 +1925,9 @@ end -- @param #string To To state. function AUFTRAG:onafterStatus(From, Event, To) + -- Current abs. mission time. + local Tnow=timer.getAbsTime() + -- Number of alive mission targets. local Ntargets=self:CountMissionTargets() @@ -1837,7 +1942,7 @@ function AUFTRAG:onafterStatus(From, Event, To) -- All groups have reported MISSON DONE. self:Done() - elseif (self.Tstop and timer.getAbsTime()>self.Tstop+10) or (self.Ntargets>0 and Ntargets==0) then + elseif (self.Tstop and Tnow>self.Tstop+10) or (self.Ntargets>0 and Ntargets==0) then -- Cancel mission if stop time passed. self:Cancel() @@ -1845,36 +1950,39 @@ function AUFTRAG:onafterStatus(From, Event, To) end end - - + -- Current FSM state. local fsmstate=self:GetState() - local Tnow=timer.getAbsTime() - -- Mission start stop time. - local Cstart=UTILS.SecondsToClock(self.Tstart, true) - local Cstop=self.Tstop and UTILS.SecondsToClock(self.Tstop, true) or "INF" - - local targetname=self:GetTargetName() or "unknown" - - local airwing=self.airwing and self.airwing.alias or "N/A" - local commander=self.wingcommander and tostring(self.wingcommander.coalition) or "N/A" - - -- Info message. - self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) - -- Check for error. if fsmstate~=self.status then self:E(self.lid..string.format("ERROR: FSM state %s != %s mission status!", fsmstate, self.status)) end - - -- Data on assigned groups. - local text="Group data:" - for groupname,_groupdata in pairs(self.groupdata) do - local groupdata=_groupdata --#AUFTRAG.GroupData - text=text..string.format("\n- %s: status mission=%s opsgroup=%s", groupname, groupdata.status, groupdata.opsgroup and groupdata.opsgroup:GetState() or "N/A") + + if self.verbose>=1 then + + -- Mission start stop time. + local Cstart=UTILS.SecondsToClock(self.Tstart, true) + local Cstop=self.Tstop and UTILS.SecondsToClock(self.Tstop, true) or "INF" + + local targetname=self:GetTargetName() or "unknown" + + local airwing=self.airwing and self.airwing.alias or "N/A" + local commander=self.wingcommander and tostring(self.wingcommander.coalition) or "N/A" + + -- Info message. + self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) + end + + if self.verbose>=2 then + -- Data on assigned groups. + local text="Group data:" + for groupname,_groupdata in pairs(self.groupdata) do + local groupdata=_groupdata --#AUFTRAG.GroupData + text=text..string.format("\n- %s: status mission=%s opsgroup=%s", groupname, groupdata.status, groupdata.opsgroup and groupdata.opsgroup:GetState() or "N/A") + end + self:I(self.lid..text) end - self:T(self.lid..text) -- Ready to evaluate mission outcome? local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index b4b3e0ce9..77dbfa768 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2184,7 +2184,7 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) local wp0=coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) local wp9=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, "Refuel") - self:Route({wp0, wp9}) + self:Route({wp0, wp9}, 1) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 14b8db4e8..e2cdffa2e 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -757,17 +757,12 @@ function OPSGROUP:GetWaypointIndexNext(cyclic) cyclic=self.adinfinitum end - --env.info("FF cyclic = "..tostring(cyclic)) - local N=#self.waypoints local n=math.min(self.currentwp+1, N) - --env.info("FF n = "..tostring(n)) - if cyclic and self.currentwp==N then n=1 - --env.info("FF cyclic n = "..tostring(n)) end return n From cf65c2af15979a2485580b77b9a644151fc352ea Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 13 Aug 2020 01:21:02 +0200 Subject: [PATCH 33/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 171 ++++++----- Moose Development/Moose/Ops/FlightGroup.lua | 96 ++---- Moose Development/Moose/Ops/NavyGroup.lua | 171 ++++++----- Moose Development/Moose/Ops/OpsGroup.lua | 306 ++++++++++++++------ Moose Development/Moose/Ops/Squadron.lua | 2 + 5 files changed, 435 insertions(+), 311 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 3ced05e52..1674f9c97 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -21,7 +21,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\ARMYGROUP\NavyGroup_Main.jpg) +-- ![Banner Image](..\Presentations\ARMYGROUP\ArmyGroup_Main.jpg) -- -- # The ARMYGROUP Concept -- @@ -33,12 +33,12 @@ ARMYGROUP = { formationPerma = nil, } ---- Navy group element. +--- Army group element. -- @type ARMYGROUP.Element -- @field #string name Name of the element, i.e. the unit. -- @field #string typename Type name. ---- NavyGroup version. +--- Army Group version. -- @field #string version ARMYGROUP.version="0.0.1" @@ -151,8 +151,7 @@ end --- Get coordinate of the closest road. -- @param #ARMYGROUP self --- @param #boolean switch If true or nil, patrol until the end of time. If false, go along the waypoints once and stop. --- @return #ARMYGROUP self +-- @return Core.Point#COORDINATE Coordinate of a road closest to the group. function ARMYGROUP:GetClosestRoad() return self:GetCoordinate():GetClosestPointToRoad() end @@ -174,6 +173,24 @@ function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Cl end +--- Add a *waypoint* task. +-- @param #ARMYGROUP self +-- @param Core.Point#COORDINATE Coordinate Coordinate of the target. +-- @param Ops.OpsGroup#OPSGROUP.Waypoint Waypoint Where the task is executed. Default is next waypoint. +-- @param #number Radius Radius in meters. Default 100 m. +-- @param #number Nshots Number of shots to fire. Default 3. +-- @param #number WeaponType Type of weapon. Default auto. +-- @param #number Prio Priority of the task. +function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio) + + Waypoint=Waypoint or self:GetWaypointNext() + + local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) + + self:AddTaskWaypoint(DCStask, Waypoint, nil, Prio) + +end + --- Add a *scheduled* task. -- @param #ARMYGROUP self -- @param Wrapper.Group#GROUP TargetGroup Target group. @@ -189,6 +206,8 @@ function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -251,60 +270,11 @@ function ARMYGROUP:onafterStatus(From, Event, To) --- - -- Tasks + -- Tasks & Missions --- - - -- Task queue. - if #self.taskqueue>0 and self.verbose>1 then - local text=string.format("Tasks #%d", #self.taskqueue) - for i,_task in pairs(self.taskqueue) do - local task=_task --Ops.OpsGroup#OPSGROUP.Task - local name=task.description - local taskid=task.dcstask.id or "unknown" - local status=task.status - local clock=UTILS.SecondsToClock(task.time, true) - local eta=task.time-timer.getAbsTime() - local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A" - local duration=-1 - if task.duration then - duration=task.duration - if task.timestamp then - -- Time the task is running. - duration=task.duration-(timer.getAbsTime()-task.timestamp) - else - -- Time the task is supposed to run. - duration=task.duration - end - end - -- Output text for element. - if task.type==OPSGROUP.TaskType.SCHEDULED then - text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration) - elseif task.type==OPSGROUP.TaskType.WAYPOINT then - text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get()) - end - end - self:I(self.lid..text) - end - - --- - -- Missions - --- - - -- Current mission name. - if self.verbose>0 then - local Mission=self:GetMissionByID(self.currentmission) - - -- Current status. - local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none") - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - local Cstart= UTILS.SecondsToClock(mission.Tstart, true) - local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF" - text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", - i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets()) - end - self:I(self.lid..text) - end + + self:_PrintTaskAndMissionStatus() + self:__Status(-30) end @@ -350,11 +320,29 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE option. - self:SwitchROE(self.option.ROE) + -- Set default ROE. + if self.option.ROE then + self:SwitchROE(self.option.ROE) + else + self:SwitchROE(ENUMS.ROE.ReturnFire) + end - -- Set default Alarm State option. - self:SwitchAlarmstate(self.option.Alarm) + -- Set default Alarm State. + if self.option.Alarm then + self:SwitchAlarmstate(self.option.Alarm) + else + self:SwitchAlarmstate(0) + end + + -- Turn TACAN beacon on. + if self.tacan.On then + self:_SwitchTACAN(self.tacan) + end + + -- Turn on the radio. + if self.radio.On then + self:SwitchRadio(self.radio.Freq, self.radio.Modu) + end end @@ -384,9 +372,12 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Waypoints. local waypoints={} + + -- Total number of waypoints + local N=#self.waypoints -- Add remaining waypoints to route. - for i=n, #self.waypoints do + for i=n, N do -- Copy waypoint. local wp=UTILS.DeepCopy(self.waypoints[i]) --Ops.OpsGroup#OPSGROUP.Waypoint @@ -404,7 +395,9 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) end if self.formationPerma then - wp.action=self.formationPerma + --if self.formationPerma==ENUMS.Formation.Vehicle.OnRoad then + wp.action=self.formationPerma + --end elseif Formation then wp.action=Formation end @@ -428,17 +421,27 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) end end - - - if formation==ENUMS.Formation.Vehicle.OnRoad then - local wpnext=self:GetWaypointNext() - - if wpnext.action~=ENUMS.Formation.Vehicle.OnRoad then - - end + if wp.roaddist>100 and wp.action==ENUMS.Formation.Vehicle.OnRoad then + env.info("FF Adding ON road waypoint") + --wp.roadcoord:MarkToAll("Added Road waypoint") + -- Waypoint is actually off road! + wp.action=ENUMS.Formation.Vehicle.OffRoad + + -- Add "On Road" waypoint in between. + local wproad=wp.roadcoord:WaypointGround(wp.speed, ENUMS.Formation.Vehicle.OnRoad) + table.insert(waypoints, wproad) + end + + --if wp.formation==ENUMS.Formation.Vehicle.OnRoad and wp.action~=ENUMS.Formation.Vehicle.OnRoad then --and not self.formationPerma~=ENUMS.Formation.Vehicle.OnRoad then + --[[ + if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>100 then + env.info("FF Adding ON road waypoint") + local wproad=wp.roadcoord:WaypointGround(wp.speed, ENUMS.Formation.Vehicle.OnRoad) + table.insert(waypoints, wproad) end + ]] -- Debug info. self:I(string.format("WP %d %s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.type, wp.speed, wp.alt, wp.action)) @@ -716,10 +719,24 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation -- Get closest point to road. waypoint.roadcoord=Coordinate:GetClosestPointToRoad(false) - waypoint.roadist=Coordinate:Get2DDistance(waypoint.roadcoord) + if waypoint.roadcoord then + waypoint.roaddist=Coordinate:Get2DDistance(waypoint.roadcoord) + else + waypoint.roaddist=1000*1000 --1000 km. + end + + --[[ + if waypoint.roaddist>100 and waypoint.action==ENUMS.Formation.Vehicle.OnRoad then + waypoint.formation=ENUMS.Formation.Vehicle.OnRoad + waypoint.action=ENUMS.Formation.Vehicle.OffRoad + else + waypoint.formation=waypoint.action + end + ]] + -- Debug info. - self:T(self.lid..string.format("Adding GROUND waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, Speed, self.currentwp, #self.waypoints)) + self:I(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action)) -- Update route. if Updateroute==nil or Updateroute==true then @@ -771,7 +788,7 @@ function ARMYGROUP:_InitGroup() self.position=self:GetCoordinate() -- Radio parameters from template. - self.radioOn=false -- Radio is always OFF for ground. + self.radio.On=false -- Radio is always OFF for ground. self.radio.Freq=133 self.radio.Modu=radio.modulation.AM @@ -810,13 +827,13 @@ function ARMYGROUP:_InitGroup() self.actype=unit:GetTypeName() -- Debug info. - local text=string.format("Initialized Navy Group %s:\n", self.groupname) + local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("AC type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radioOn)) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 77dbfa768..fdbcae847 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -824,60 +824,10 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end --- - -- Tasks + -- Tasks & Missions --- - -- Task queue. - if self.verbose>1 and #self.taskqueue>0 then - local text=string.format("Tasks #%d", #self.taskqueue) - for i,_task in pairs(self.taskqueue) do - local task=_task --Ops.OpsGroup#OPSGROUP.Task - local name=task.description - local taskid=task.dcstask.id or "unknown" - local status=task.status - local clock=UTILS.SecondsToClock(task.time, true) - local eta=task.time-timer.getAbsTime() - local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A" - local duration=-1 - if task.duration then - duration=task.duration - if task.timestamp then - -- Time the task is running. - duration=task.duration-(timer.getAbsTime()-task.timestamp) - else - -- Time the task is supposed to run. - duration=task.duration - end - end - -- Output text for element. - if task.type==OPSGROUP.TaskType.SCHEDULED then - text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration) - elseif task.type==OPSGROUP.TaskType.WAYPOINT then - text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get()) - end - end - self:I(self.lid..text) - end - - --- - -- Missions - --- - - -- Current mission name. - if self.verbose>0 then - local Mission=self:GetMissionByID(self.currentmission) - - -- Current status. - local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none") - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - local Cstart= UTILS.SecondsToClock(mission.Tstart, true) - local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF" - text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", - i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets()) - end - self:I(self.lid..text) - end + self:_PrintTaskAndMissionStatus() --- -- Fuel State @@ -1415,35 +1365,35 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE and ROT options. - self:SwitchROE(self.option.ROE) - self:SwitchROT(self.option.ROT) - - -- Turn TACAN beacon on. - if self.tacanDefault then - self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse, self.tacanDefault.BeaconName, self.tacanDefault.Band) + -- Set default ROE. + if self.option.ROE then + self:SwitchROE(self.option.ROE) + else + self:SwitchROE(ENUMS.ROE.ReturnFire) end - -- Turn on the radio. - if self.radioDefault then - self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) + -- Set default ROT. + if self.option.ROT then + self:SwitchROT(self.option.ROT) else - self:SetDefaultRadio(self.radio.Freq, self.radio.Modu) + self:SwitchROE(ENUMS.ROT.PassiveDefense) + end + + -- Turn TACAN beacon on. + if self.tacan.On then + self:_SwitchTACAN(self.tacan) end - -- Set callsign. - if self.callsignDefault then - self:SwitchCallsign(self.callsignDefault.NumberSquad, self.callsignDefault.NumberGroup) - else - self:SetDefaultCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) + -- Turn on the radio. + if self.radio.On then + self:SwitchRadio(self.radio.Freq, self.radio.Modu) end -- TODO: make this input. self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true) - self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) -- Does not seem to work. AI still used the after burner. + self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) -- Does not seem to work. AI still used the after burner. self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) - --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) - + --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) -- Update route. self:__UpdateRoute(-0.5) @@ -2554,7 +2504,7 @@ function FLIGHTGROUP:_InitGroup() self.position=self:GetCoordinate() -- Radio parameters from template. - self.radioOn=self.template.communication + self.radio.On=self.template.communication self.radio.Freq=self.template.frequency self.radio.Modu=self.template.modulation @@ -2614,7 +2564,7 @@ function FLIGHTGROUP:_InitGroup() text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radioOn)) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 8cf2a117a..940fcf4b2 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -95,8 +95,8 @@ function NAVYGROUP:New(GroupName) self.lid=string.format("NAVYGROUP %s | ", self.groupname) -- Defaults - self:SetDefaultROE() - self:SetDefaultAlarmstate() + --self:SetDefaultROE() + --self:SetDefaultAlarmstate() self:SetDetection() self:SetPatrolAdInfinitum(true) @@ -205,6 +205,27 @@ function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Cl return task end +--- Add a *waypoint* task. +-- @param #NAVYGROUP self +-- @param Core.Point#COORDINATE Coordinate Coordinate of the target. +-- @param Ops.OpsGroup#OPSGROUP.Waypoint Waypoint Where the task is executed. Default is next waypoint. +-- @param #number Radius Radius in meters. Default 100 m. +-- @param #number Nshots Number of shots to fire. Default 3. +-- @param #number WeaponType Type of weapon. Default auto. +-- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. +function NAVYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio) + + Waypoint=Waypoint or self:GetWaypointNext() + + local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) + + local task=self:AddTaskWaypoint(DCStask, Waypoint, nil, Prio) + + return task +end + + --- Add a *scheduled* task. -- @param #NAVYGROUP self -- @param Wrapper.Group#GROUP TargetGroup Target group. @@ -408,14 +429,16 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check free path ahead. freepath=self:_CheckFreePath(freepath, 100) - if freepath<5000 then + if freepath<5000 and not self.collisionwarning then + + -- Issue a collision warning event. self:CollisionWarning() end if not self.ispathfinding then if freepath<5000 then - --self.ispathfinding=self:_FindPathToNextWaypoint() + self.ispathfinding=self:_FindPathToNextWaypoint() end end @@ -464,60 +487,10 @@ function NAVYGROUP:onafterStatus(From, Event, To) --- - -- Tasks + -- Tasks & Missions --- - - -- Task queue. - if self.verbose>-1 and #self.taskqueue>0 then - local text=string.format("Tasks #%d", #self.taskqueue) - for i,_task in pairs(self.taskqueue) do - local task=_task --Ops.OpsGroup#OPSGROUP.Task - local name=task.description - local taskid=task.dcstask.id or "unknown" - local status=task.status - local clock=UTILS.SecondsToClock(task.time, true) - local eta=task.time-timer.getAbsTime() - local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A" - local duration=-1 - if task.duration then - duration=task.duration - if task.timestamp then - -- Time the task is running. - duration=task.duration-(timer.getAbsTime()-task.timestamp) - else - -- Time the task is supposed to run. - duration=task.duration - end - end - -- Output text for element. - if task.type==OPSGROUP.TaskType.SCHEDULED then - text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration) - elseif task.type==OPSGROUP.TaskType.WAYPOINT then - text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get()) - end - end - self:I(self.lid..text) - end - - --- - -- Missions - --- - - -- Current mission name. - if self.verbose>0 then - local Mission=self:GetMissionByID(self.currentmission) - - -- Current status. - local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none") - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - local Cstart= UTILS.SecondsToClock(mission.Tstart, true) - local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF" - text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", - i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets()) - end - self:I(self.lid..text) - end + + self:_PrintTaskAndMissionStatus() -- Next status update in 30 seconds. @@ -564,28 +537,36 @@ function NAVYGROUP:onafterSpawned(From, Event, To) self:I(self.lid..string.format("Group spawned!")) if self.ai then - + -- Set default ROE. - self:SwitchROE(self.option.ROE) + if self.option.ROE then + self:SwitchROE(self.option.ROE) + else + self:SwitchROE(ENUMS.ROE.ReturnFire) + end -- Set default Alarm State. - self:SwitchAlarmstate(self.option.Alarm) + if self.option.Alarm then + self:SwitchAlarmstate(self.option.Alarm) + else + self:SwitchAlarmstate(0) + end -- Turn TACAN beacon on. - if self.tacanDefault then - self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse, self.tacanDefault.BeaconName, self.tacanDefault.Band) + if self.tacan.On then + self:_SwitchTACAN(self.tacan) end -- Turn ICLS on. - if self.iclsDefault then - self:SwitchICLS(self.iclsDefault.Channel, self.iclsDefault.Morse, self.iclsDefault.BeaconName) + if self.icls.On then + self:_SwitchICLS(self.icls) end -- Turn on the radio. - if self.radioDefault then - self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) + if self.radio.On then + self:SwitchRadio(self.radio.Freq, self.radio.Modu) else - self:SetDefaultRadio(self.radio.Freq, self.radio.Modu) + self.radio.On=true -- Radio is always on for ships. If not set, it is default. end end @@ -776,7 +757,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) IntoWind.waypoint=wptiw - if IntoWind.Uturn then + if IntoWind.Uturn and self.Debug then IntoWind.Coordinate:MarkToAll("Return coord") end @@ -880,6 +861,34 @@ function NAVYGROUP:onafterSurface(From, Event, To, Speed) end +--- On after "TurningStarted" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function NAVYGROUP:onafterTurningStarted(From, Event, To) + self.turning=true +end + +--- On after "TurningStarted" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function NAVYGROUP:onafterTurningStopped(From, Event, To) + self.turning=false + self.collisionwarning=false +end + +--- On after "CollisionWarning" event. +-- @param #NAVYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function NAVYGROUP:onafterCollisionWarning(From, Event, To) + self.collisionwarning=true +end + --- On after "Dead" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -1029,6 +1038,16 @@ end -- @param #boolean Updateroute If true or nil, call UpdateRoute. If false, no call. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Updateroute) + + if not Coordinate:IsInstanceOf("COORDINATE") then + if Coordinate:IsInstanceOf("POSITIONABLE") then + self:I(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE. Trying to get coordinate") + Coordinate=Coordinate:GetCoordinate() + else + self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE!") + return nil + end + end -- Set waypoint index. local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) @@ -1107,7 +1126,7 @@ function NAVYGROUP:_InitGroup() self.position=self:GetCoordinate() -- Radio parameters from template. - self.radioOn=true -- Radio is always on for ships. + self.radio.On=false -- Radio is always on for ships but we set it to false to check if it has been changed before spawn. self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 self.radio.Modu=tonumber(self.template.units[1].modulation) @@ -1152,7 +1171,7 @@ function NAVYGROUP:_InitGroup() text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radioOn)) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) text=text..string.format("FSM state = %s\n", self:GetState()) text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) @@ -1190,7 +1209,13 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) return distance end - local offsetY=1 + -- Offset above sea level. + local offsetY=0.1 + + -- Current bug on Caucasus. LoS returns false. + if UTILS.GetDCSMap()==DCSMAP.Caucasus then + offsetY=5.01 + end -- Current coordinate. local coordinate=self:GetCoordinate():SetAltitude(offsetY, true) @@ -1199,7 +1224,7 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local heading=self:GetHeading() -- Check from 500 meters in front. - coordinate=coordinate:Translate(500, heading, true) + --coordinate=coordinate:Translate(500, heading, true) local function LoS(dist) local checkcoord=coordinate:Translate(dist, heading, true) @@ -1534,7 +1559,7 @@ function NAVYGROUP:_FindPathToNextWaypoint() local delta=dist/10 -- Create a grid of nodes. We only want nodes of surface type water. - astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta*2, false) + astar:CreateGrid({land.SurfaceType.WATER}, boxwidth, spacex, delta, delta*2, self.Debug) -- Valid neighbour nodes need to have line of sight. astar:SetValidNeighbourLoS(400) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index e2cdffa2e..1a83902b8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -58,16 +58,13 @@ -- -- @field #OPSGROUP.Radio radio Current radio settings. -- @field #OPSGROUP.Radio radioDefault Default radio settings. --- @field #boolean radioOn If true, radio is currently turned on. -- @field Core.RadioQueue#RADIOQUEUE radioQueue Radio queue. -- -- @field #OPSGROUP.Beacon tacan Current TACAN settings. -- @field #OPSGROUP.Beacon tacanDefault Default TACAN settings. --- @field #boolean tacanOn If true, TACAN is currently active. -- -- @field #OPSGROUP.Beacon icls Current ICLS settings. -- @field #OPSGROUP.Beacon iclsDefault Default ICLS settings. --- @field #boolean iclsOn If true, ICLS is currently active. -- -- @field #OPSGROUP.Option option Current optional settings. -- @field #OPSGROUP.Option optionDefault Default option settings. @@ -202,11 +199,13 @@ OPSGROUP.TaskType={ -- @field #string Band Band "X" or "Y" for TACAN beacon. -- @field #string BeaconName Name of the unit acting as beacon. -- @field Wrapper.Unit#UNIT BeaconUnit Unit object acting as beacon. +-- @field #boolean On If true, beacon is on, if false, beacon is turned off. If nil, has not been used yet. --- Radio data. -- @type OPSGROUP.Radio -- @field #number Freq Frequency -- @field #number Modu Modulation. +-- @field #boolean On If true, radio is on, if false, radio is turned off. If nil, has not been used yet. --- Callsign data. -- @type OPSGROUP.Callsign @@ -255,7 +254,9 @@ OPSGROUP.TaskType={ -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. -- @field Core.Point#COORDINATE roadcoord Closest point to road. +-- @field #number roaddist Distance to closest point on road. -- @field Wrapper.Marker#MARKER marker Marker on the F10 map. +-- @field #string formation Ground formation. Similar to action but on/off road. --- NavyGroup version. -- @field #string version @@ -864,6 +865,14 @@ end --- Get unique ID of waypoint. -- @param #OPSGROUP self +-- @param #OPSGROUP.Waypoint waypoint The waypoint data table. +-- @return #number Unique ID. +function OPSGROUP:GetWaypointUID(waypoint) + return waypoint.uid +end + +--- Get unique ID of waypoint given its index. +-- @param #OPSGROUP self -- @param #number indx Waypoint index. -- @return #number Unique ID. function OPSGROUP:GetWaypointID(indx) @@ -1213,7 +1222,7 @@ function OPSGROUP:AddTaskWaypoint(task, Waypoint, description, prio, duration) -- Task data structure. local newtask={} --#OPSGROUP.Task - newtask.description=description + newtask.description=description or string.format("Task #%d", self.taskcounter) newtask.status=OPSGROUP.TaskStatus.SCHEDULED newtask.dcstask=task newtask.prio=prio or 50 @@ -2592,6 +2601,72 @@ function OPSGROUP:_CheckGroupDone(delay) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status Info Common to Air, Land and Sea +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Print info on mission and task status to DCS log file. +-- @param #OPSGROUP self +function OPSGROUP:_PrintTaskAndMissionStatus() + + --- + -- Tasks: verbose >= 2 + --- + + -- Task queue. + if #self.taskqueue>0 and self.verbose>=2 then + local text=string.format("Tasks #%d", #self.taskqueue) + for i,_task in pairs(self.taskqueue) do + local task=_task --Ops.OpsGroup#OPSGROUP.Task + local name=task.description + local taskid=task.dcstask.id or "unknown" + local status=task.status + local clock=UTILS.SecondsToClock(task.time, true) + local eta=task.time-timer.getAbsTime() + local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A" + local duration=-1 + if task.duration then + duration=task.duration + if task.timestamp then + -- Time the task is running. + duration=task.duration-(timer.getAbsTime()-task.timestamp) + else + -- Time the task is supposed to run. + duration=task.duration + end + end + -- Output text for element. + if task.type==OPSGROUP.TaskType.SCHEDULED then + text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration) + elseif task.type==OPSGROUP.TaskType.WAYPOINT then + text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get()) + end + end + self:I(self.lid..text) + end + + --- + -- Missions: verbose>=1 + --- + + -- Current mission name. + if self.verbose>0 then + local Mission=self:GetMissionByID(self.currentmission) + + -- Current status. + local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none") + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + local Cstart= UTILS.SecondsToClock(mission.Tstart, true) + local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF" + text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", + i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets()) + end + self:I(self.lid..text) + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoints & Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2653,9 +2728,13 @@ function OPSGROUP:InitWaypoints() for index,wp in pairs(self.waypoints0) do - local waypoint=self:_CreateWaypoint(wp) + --local waypoint=self:_CreateWaypoint(wp) + --self:_AddWaypoint(waypoint) - self:_AddWaypoint(waypoint) + local coordinate=COORDINATE:New(wp.x, wp.alt, wp.y) + local speedknots=UTILS.MpsToKnots(wp.speed) + + self:AddWaypoint(coordinate, speedknots, index-1, nil, false) end @@ -2871,19 +2950,26 @@ end --- Set current ROE for the group. -- @param #OPSGROUP self --- @param #string roe ROE of group. Default is the value defined by :SetDefaultROE(). +-- @param #string roe ROE of group. Default is `ENUMS.ROE.ReturnFire`. -- @return #OPSGROUP self function OPSGROUP:SwitchROE(roe) + + if self:IsAlive() or self:IsInUtero() then - self.option.ROE=roe or self.optionDefault.ROE + self.option.ROE=roe or ENUMS.ROE.ReturnFire - if self:IsAlive() then - - self.group:OptionROE(self.option.ROE) + if self:IsInUtero() then + self:I(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED", self.option.ROE)) + else + + self.group:OptionROE(roe) + + self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) + end + - self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) else - -- TODO WARNING + self:E(self.lid.."WARNING: Cannot switch ROE! Group is not alive") end return self @@ -2907,19 +2993,26 @@ end --- Set ROT for the group. -- @param #OPSGROUP self --- @param #string rot ROT of group. Default is the value defined by :SetDefaultROT(). +-- @param #string rot ROT of group. Default is `ENUMS.ROT.PassiveDefense`. -- @return #OPSGROUP self function OPSGROUP:SwitchROT(rot) - - self.option.ROT=rot or self.optionDefault.ROT - if self:IsAlive() then + if self:IsAlive() or self:IsInUtero() then - self.group:OptionROT(self.option.ROT) + self.option.ROT=rot or ENUMS.ROT.PassiveDefense + + if self:IsInUtero() then + self:I(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) + else - self:T2(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + self.group:OptionROT(self.option.ROT) + + self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + end + + else - -- TODO WARNING + self:E(self.lid.."WARNING: Cannot switch ROT! Group is not alive") end return self @@ -2943,29 +3036,41 @@ function OPSGROUP:SetDefaultAlarmstate(alarmstate) end --- Set current Alarm State of the group. +-- +-- * 0 = "Auto" +-- * 1 = "Green" +-- * 2 = "Red" +-- -- @param #OPSGROUP self --- @param #string alarmstate Alarm state of group. Default is the value defined by :SetDefaultAlarmstate(). +-- @param #number alarmstate Alarm state of group. Default is 0="Auto". -- @return #OPSGROUP self function OPSGROUP:SwitchAlarmstate(alarmstate) - - self.option.Alarm=alarmstate or self.optionDefault.Alarm - if self:IsAlive() then + if self:IsAlive() or self:IsInUtero() then - if self.option.Alarm==0 then - self.group:OptionAlarmStateAuto() - elseif self.option.Alarm==1 then - self.group:OptionAlarmStateGreen() - elseif self.option.Alarm==2 then - self.group:OptionAlarmStateRed() - else - self:E("ERROR: Unknown Alarm State! Setting to AUTO.") - self.group:OptionAlarmStateAuto() - end + self.option.Alarm=alarmstate or 0 - self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) + if self:IsInUtero() then + self:I(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED", self.option.Alarm)) + else + + if self.option.Alarm==0 then + self.group:OptionAlarmStateAuto() + elseif self.option.Alarm==1 then + self.group:OptionAlarmStateGreen() + elseif self.option.Alarm==2 then + self.group:OptionAlarmStateRed() + else + self:E("ERROR: Unknown Alarm State! Setting to AUTO") + self.group:OptionAlarmStateAuto() + self.option.Alarm=0 + end + + self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) + + end else - -- TODO WARNING + self:E(self.lid.."WARNING: Cannot switch Alarm State! Group is not alive.") end return self @@ -2996,6 +3101,15 @@ function OPSGROUP:SetDefaultTACAN(Channel, Morse, UnitName, Band) return self end + +--- Activate/switch TACAN beacon settings. +-- @param #OPSGROUP self +-- @param #OPSGROUP.Beacon Tacan Tacan data table. +-- @return #OPSGROUP self +function OPSGROUP:_SwitchTACAN(Tacan) + self:SwitchTACAN(Tacan.Channel, Tacan.Morse, Tacan.BeaconName, Tacan.Band) +end + --- Activate/switch TACAN beacon settings. -- @param #OPSGROUP self -- @param #number Channel TACAN Channel. @@ -3005,7 +3119,7 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) - if self:IsAlive() then + if self:IsAlive() or self:IsInUtero() then local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT @@ -3022,15 +3136,10 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) unit=self.group:GetUnit(1) end - if not Channel then - Channel=self.tacanDefault and self.tacanDefault.Channel or nil - end + Channel=Channel or 74 + Morse=Morse or "XXX" - if not Morse then - Morse=self.tacanDefault and self.tacanDefault.Morse or "XXX" - end - - if unit and unit:IsAlive() and Channel then + if unit and unit:IsAlive() or self:IsInUtero() then local UnitID=unit:GetID() @@ -3039,7 +3148,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) if self.isAircraft then System=BEACON.System.TACAN_TANKER_Y - Band=Band or "Y" + Band=Band or "Y" else System=BEACON.System.TACAN Band=Band or "X" @@ -3048,26 +3157,31 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) -- Tacan frequency. local Frequency=UTILS.TACANToFrequency(Channel, Band) - -- Activate beacon. - unit:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Band, true, Morse, true) - -- Update info. - self.tacan={} self.tacan.Channel=Channel self.tacan.Morse=Morse self.tacan.Band=Band self.tacan.BeaconName=unit:GetName() self.tacan.BeaconUnit=unit + self.tacan.On=true - -- TACAN is now on. - self.tacanOn=true - - self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) + if self:IsInUtero() then + self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s when GROUP is SPAWNED", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) + else + + -- Activate beacon. + unit:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Band, true, Morse, true) + + self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) + end + else - self:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive.") + self:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive") end + else + self:E(self.lid.."ERROR: Cound not set TACAN! Group is not alive") end return self @@ -3107,13 +3221,21 @@ end --- Activate/switch ICLS beacon settings. -- @param #OPSGROUP self --- @param #number Channel ICLS Channel. --- @param #string Morse ICLS morse code. Default is the value set in @{#OPSGROUP.SetDefaultICLS} or if not set "XXX". +-- @param #OPSGROUP.Beacon Icls ICLS data table. +-- @return #OPSGROUP self +function OPSGROUP:_SwitchICLS(Icls) + self:SwitchICLS(Icls.Channel, Icls.Morse, Icls.BeaconName) +end + +--- Activate/switch ICLS beacon settings. +-- @param #OPSGROUP self +-- @param #number Channel ICLS Channel. Default is 1. +-- @param #string Morse ICLS morse code. Default is "XXX". -- @param #string UnitName Name of the unit in the group which should activate the ICLS beacon. Can also be given as #number to specify the unit number. Default is the first unit of the group. -- @return #OPSGROUP self function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) - if self:IsAlive() then + if self:IsAlive() or self:IsInUtero() then local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT @@ -3130,33 +3252,32 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) unit=self.group:GetUnit(1) end - if not Channel then - Channel=self.iclsDefault and self.iclsDefault.Channel or nil - end - - if not Morse then - Morse=self.iclsDefault and self.iclsDefault.Morse or "XXX" - end + Channel=Channel or 1 + Morse=Morse or "XXX" - if unit and unit:IsAlive() and Channel then + if unit and unit:IsAlive() or self:IsInUtero() then local UnitID=unit:GetID() - - -- Activate beacon. - unit:CommandActivateICLS(Channel, UnitID, Morse) - + -- Update info. - self.icls={} self.icls.Channel=Channel self.icls.Morse=Morse - self.icls.Band=Band + self.icls.Band=nil self.icls.BeaconName=unit:GetName() self.icls.BeaconUnit=unit + self.icls.On=true + - -- ICLS is now on. - self.iclsOn=true + if self:IsInUtero() then + self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) + else - self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) + -- Activate beacon. + unit:CommandActivateICLS(Channel, UnitID, Morse) + + self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) + + end else self:E(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") @@ -3204,33 +3325,42 @@ function OPSGROUP:GetRadio() return self.radio.Freq, self.radio.Modu end ---- Turn radio on. +--- Turn radio on or switch frequency/modulation. -- @param #OPSGROUP self --- @param #number Frequency Radio frequency in MHz. +-- @param #number Frequency Radio frequency in MHz. Default is 127.5 MHz. -- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. -- @return #OPSGROUP self function OPSGROUP:SwitchRadio(Frequency, Modulation) - if self:IsAlive() and Frequency then + if self:IsAlive() or self:IsInUtero() then - Modulation=Modulation or self.radioDefault.Modu + Frequency=Frequency or 127.5 + Modulation=Modulation or radio.modulation.AM local group=self.group --Wrapper.Group#GROUP - if self.isAircraft and not self.radioOn then + if self.isAircraft and not self.radio.On then group:SetOption(AI.Option.Air.id.SILENCE, false) end - group:CommandSetFrequency(Frequency, Modulation) - + -- Set radio self.radio.Freq=Frequency self.radio.Modu=Modulation + self.radio.On=true - -- Radio is on. - self.radioOn=true - - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + if self:IsInUtero() then + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + else + -- Give command + group:CommandSetFrequency(Frequency, Modulation) + + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + + end + + else + self:E(self.lid.."ERROR: Cound not set Radio! Group is not alive") end return self @@ -3252,11 +3382,11 @@ function OPSGROUP:TurnOffRadio() --self.radio.Modu=nil -- Radio is off. - self.radioOn=false + self.radio.On=false self:I(self.lid..string.format("Switching radio OFF")) else - self:E(self.lid.."ERROR radio can only be turned off for aircraft!") + self:E(self.lid.."ERROR: Radio can only be turned off for aircraft!") end end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 0c1939570..ac57544e4 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -156,8 +156,10 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "OnDuty") -- Start FSM. self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("OnDuty", "Pause", "Paused") -- Pause squadron. self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause squadron. + self:AddTransition("*", "Stop", "Stopped") -- Stop squadron. From 200c1dac85d1a70744ca31d12f33a1d5ecb2cfac Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 14 Aug 2020 01:01:45 +0200 Subject: [PATCH 34/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 22 +++++----- Moose Development/Moose/Ops/NavyGroup.lua | 51 ++++++++++++----------- Moose Development/Moose/Ops/OpsGroup.lua | 33 ++++++++------- 3 files changed, 58 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 1674f9c97..d3d1335b9 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -233,17 +233,18 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() + + if self:IsAlive() then + + --- + -- Detection + --- + + -- Check if group has detected any units. + if self.detectionOn then + self:_CheckDetectedUnits() + end - --- - -- Detection - --- - - -- Check if group has detected any units. - if self.detectionOn then - self:_CheckDetectedUnits() - end - - if self:IsAlive() and not self:IsDead() then -- Current heading and position of the carrier. local hdg=self:GetHeading() @@ -276,6 +277,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_PrintTaskAndMissionStatus() + -- Next status update. self:__Status(-30) end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 940fcf4b2..cc6b775cc 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -95,10 +95,9 @@ function NAVYGROUP:New(GroupName) self.lid=string.format("NAVYGROUP %s | ", self.groupname) -- Defaults - --self:SetDefaultROE() - --self:SetDefaultAlarmstate() self:SetDetection() self:SetPatrolAdInfinitum(true) + self:SetPathfinding(false) -- Add FSM transitions. -- From State --> Event --> To State @@ -181,7 +180,7 @@ end --- Enable/disable pathfinding. -- @param #NAVYGROUP self --- @param #boolean switch If true, enable pathfinding. +-- @param #boolean Switch If true, enable pathfinding. -- @return #NAVYGROUP self function NAVYGROUP:SetPathfinding(Switch) self.pathfindingOn=Switch @@ -400,17 +399,17 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() - - --- - -- Detection - --- - -- Check if group has detected any units. - if self.detectionOn then - self:_CheckDetectedUnits() - end + if self:IsAlive() then - if self:IsAlive() and not self:IsDead() then + --- + -- Detection + --- + + -- Check if group has detected any units. + if self.detectionOn then + self:_CheckDetectedUnits() + end -- Current heading and position of the carrier. local hdg=self:GetHeading() @@ -420,8 +419,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group started or stopped turning. self:_CheckTurning() - local freepath=10000 - local collision=false + local freepath=UTILS.NMToMeters(10) -- Only check if not currently turning. if not self:IsTurning() then @@ -429,15 +427,14 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check free path ahead. freepath=self:_CheckFreePath(freepath, 100) - if freepath<5000 and not self.collisionwarning then + if freepath<5000 then - -- Issue a collision warning event. - self:CollisionWarning() - end + if not self.collisionwarning then + -- Issue a collision warning event. + self:CollisionWarning(freepath) + end - if not self.ispathfinding then - - if freepath<5000 then + if self.pathfindingOn and not self.ispathfinding then self.ispathfinding=self:_FindPathToNextWaypoint() end @@ -885,7 +882,9 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function NAVYGROUP:onafterCollisionWarning(From, Event, To) +-- @param #number Distance Distance in meters where obstacle was detected. +function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance) + self:I(self.lid..string.format("Iceberg ahead in %d meters!", Distance or -1)) self.collisionwarning=true end @@ -1039,8 +1038,9 @@ end -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Updateroute) + -- Check if a coordinate was given or at least a positionable. if not Coordinate:IsInstanceOf("COORDINATE") then - if Coordinate:IsInstanceOf("POSITIONABLE") then + if Coordinate:IsInstanceOf("POSITIONABLE") or Coordinate:IsInstanceOf("ZONE_BASE") then self:I(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE. Trying to get coordinate") Coordinate=Coordinate:GetCoordinate() else @@ -1198,6 +1198,8 @@ end --- Check for possible collisions between two coordinates. -- @param #NAVYGROUP self +-- @param #number DistanceMax Max distance in meters ahead to check. Default 5000. +-- @param #number dx -- @return #number Free distance in meters. function NAVYGROUP:_CheckFreePath(DistanceMax, dx) @@ -1252,7 +1254,8 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local los=LoS(x) - env.info(string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) + -- Debug message. + self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) if los and d<=eps then return x diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1a83902b8..fd5f5b6f8 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2292,26 +2292,31 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #number n The goto waypoint number. -function OPSGROUP:onafterGotoWaypoint(From, Event, To, n) +-- @param #number UID The goto waypoint unique ID. +function OPSGROUP:onafterGotoWaypoint(From, Event, To, UID) - -- The last waypoint passed was n-1 - self.currentwp=n-1 + local n=self:GetWaypointIndex(UID) - -- TODO: switch to re-enable waypoint tasks. - if false then - local tasks=self:GetTasksWaypoint(n) - - for _,_task in pairs(tasks) do - local task=_task --#OPSGROUP.Task - task.status=OPSGROUP.TaskStatus.SCHEDULED + if n then + + -- TODO: switch to re-enable waypoint tasks. + if false then + local tasks=self:GetTasksWaypoint(n) + + for _,_task in pairs(tasks) do + local task=_task --#OPSGROUP.Task + task.status=OPSGROUP.TaskStatus.SCHEDULED + end + end + local Speed=self:GetSpeedToWaypoint(n) + + -- Update the route. + self:__UpdateRoute(-1, n, Speed) + end - -- Update the route. - self:UpdateRoute() - end --- On after "DetectedUnit" event. Add newly detected unit to detected units set. From 32c9a59ff0ee785938c8b11105be1f4887e08c7c Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 14 Aug 2020 18:09:13 +0200 Subject: [PATCH 35/79] Ops --- Moose Development/Moose/Core/Astar.lua | 2 +- Moose Development/Moose/Ops/AirWing.lua | 65 +++++++++++++++++++---- Moose Development/Moose/Ops/Auftrag.lua | 65 ++++++++++++++++------- Moose Development/Moose/Ops/OpsGroup.lua | 11 +++- Moose Development/Moose/Ops/Squadron.lua | 5 +- Moose Development/Moose/Ops/Target.lua | 6 +-- Moose Development/Moose/Wrapper/Group.lua | 26 +++++++++ 7 files changed, 143 insertions(+), 37 deletions(-) diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 984f67df0..435bec5a4 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -550,7 +550,7 @@ end -- @return #number Distance between the two nodes. function ASTAR.DistRoad(nodeA, nodeB) - local path, dist, gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate,IncludeEndpoints,Railroad,MarkPath,SmokePath) + local path, dist, gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate, IncludeEndpoints, Railroad, MarkPath, SmokePath) if gotpath then return dist diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 0138f78b4..40e65adad 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -116,6 +116,7 @@ AIRWING = { squadrons = {}, missionqueue = {}, payloads = {}, + payloadcounter = 0, pointsCAP = {}, pointsTANKER = {}, pointsAWACS = {}, @@ -132,6 +133,7 @@ AIRWING = { --- Payload data. -- @type AIRWING.Payload +-- @field #number uid Unique payload ID. -- @field #string unitname Name of the unit this pylon was extracted from. -- @field #string aircrafttype Type of aircraft, which can use this payload. -- @field #table capabilities Mission types and performances for which this payload can be used. @@ -309,7 +311,8 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) end -- Create payload. - local payload={} --#AIRWING.Payload + local payload={} --#AIRWING.Payload + payload.uid=self.payloadcounter payload.unitname=Unit:GetName() payload.aircrafttype=Unit:GetTypeName() payload.pylons=Unit:GetTemplatePayload() @@ -343,6 +346,9 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) -- Add payload table.insert(self.payloads, payload) + -- Increase counter + self.payloadcounter=self.payloadcounter+1 + return payload end @@ -385,8 +391,9 @@ end -- @param #AIRWING self -- @param #string UnitType The type of the unit. -- @param #string MissionType The mission type. +-- @param #table Payloads Specific payloads only to be considered. -- @return #AIRWING.Payload Payload table or *nil*. -function AIRWING:FetchPayloadFromStock(UnitType, MissionType) +function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) -- Quick check if we have any payloads. if not self.payloads or #self.payloads==0 then @@ -427,11 +434,25 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType) end end + local function _checkPayloads(payload) + if Payloads then + for _,Payload in pairs(Payloads) do + if Payload.uid==payload.id then + return true + end + end + else + -- Payload was not specified. + return true + end + return false + end + -- Pre-selection: filter out only those payloads that are valid for the airframe and mission type and are available. local payloads={} for _,_payload in pairs(self.payloads) do local payload=_payload --#AIRWING.Payload - if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType, payload.capabilities) and payload.navail>0 then + if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType, payload.capabilities) and payload.navail>0 and _checkPayloads(payload) then table.insert(payloads, payload) end end @@ -1119,6 +1140,15 @@ function AIRWING:_GetNextMission() end table.sort(self.missionqueue, _sort) + -- Look for first mission that is SCHEDULED. + local importance=math.huge + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + if mission.importance Date: Sat, 15 Aug 2020 00:54:36 +0200 Subject: [PATCH 36/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 8 +-- Moose Development/Moose/Ops/Auftrag.lua | 78 +++++++++++++++------ Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 5 +- Moose Development/Moose/Ops/Squadron.lua | 4 +- Moose Development/Moose/Ops/Target.lua | 29 ++++++++ Moose Development/Moose/Utilities/Enums.lua | 11 +++ 7 files changed, 108 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index d3d1335b9..796ff6229 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -157,15 +157,15 @@ function ARMYGROUP:GetClosestRoad() end ---- Add a *scheduled* task. +--- Add a *scheduled* task to fire at a given coordinate. -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. +-- @param #string Clock Time when to start the attack. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. --- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. -function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Clock, Prio) +function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) @@ -173,7 +173,7 @@ function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Cl end ---- Add a *waypoint* task. +--- Add a *waypoint* task to fire at a given coordinate. -- @param #ARMYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. -- @param Ops.OpsGroup#OPSGROUP.Waypoint Waypoint Where the task is executed. Default is next waypoint. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 0c01db6e3..f808174ec 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -107,8 +107,9 @@ -- -- @field #number optionROE ROE. -- @field #number optionROT ROT. --- @field #number optionCM Counter measures. +-- @field #number optionAlarm Alarm state. -- @field #number optionFormation Formation. +-- @field #number optionCM Counter measures. -- @field #number optionRTBammo RTB on out-of-ammo. -- @field #number optionRTBfuel RTB on out-of-fuel. -- @field #number optionECM ECM. @@ -431,7 +432,7 @@ AUFTRAG.version="0.3.1" -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Option to assign a specific payload for the mission (requires an AIRWING). +-- DONE: Option to assign a specific payload for the mission (requires an AIRWING). -- TODO: Mission success options damaged, destroyed. -- TODO: Recon mission. What input? Set of coordinates? -- NOPE: Clone mission. How? Deepcopy? ==> Create a new auftrag. @@ -1169,6 +1170,7 @@ function AUFTRAG:NewTargetAir(Target) self.engageTarget=Target + --[[ if Target.category==TARGET.Category.GROUND then @@ -1184,14 +1186,18 @@ function AUFTRAG:NewTargetAir(Target) end + ]] - local mission=self:NewAUTO() + local target=self.engageTarget:GetObject() + + local mission=self:NewAUTO(target) if mission then mission:SetPriority(10, true) end + return mission end @@ -1411,6 +1417,15 @@ function AUFTRAG:SetRepeatOnFailure(Nrepeat) return self end +--- Set how many times the mission is repeated if it was successful. +-- @param #AUFTRAG self +-- @param #number Nrepeat Number of repeats. Default 0. +-- @return #AUFTRAG self +function AUFTRAG:SetRepeatOnSuccess(Nrepeat) + self.NrepeatSuccess=Nrepeat or 0 + return self +end + --- Define how many assets are required to do the job. -- @param #AUFTRAG self -- @param #number Nassets Number of asset groups. Default 1. @@ -1549,6 +1564,17 @@ function AUFTRAG:SetROT(rot) return self end +--- Set alarm state for this mission. +-- @param #AUFTRAG self +-- @param #number Alarmstate Alarm state 0=Auto, 1=Green, 2=Red. +-- @return #AUFTRAG self +function AUFTRAG:SetAlarmstate(Alarmstate) + + self.optionAlarm=Alarmstate + + return self +end + --- Set formation for this mission. -- @param #AUFTRAG self -- @param #number Formation Formation. @@ -1688,7 +1714,7 @@ function AUFTRAG:AssignSquadrons(Squadrons) self.squadrons=Squadrons end ---- Set the required payload for this mission. +--- Set the required payload for this mission. Only available for use with an AIRWING. -- @param #AUFTRAG self -- @param Ops.AirWing#AIRWING.Payload Required payload -- @return #AUFTRAG self @@ -2463,21 +2489,6 @@ function AUFTRAG:onafterAssetDead(From, Event, To, Asset) end ---- On after "Success" event. --- @param #AUFTRAG self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function AUFTRAG:onafterSuccess(From, Event, To) - - self.status=AUFTRAG.Status.SUCCESS - self:T(self.lid..string.format("New mission status=%s", self.status)) - - -- Stop mission. - self:Stop() - -end - --- On after "Cancel" event. Cancells the mission. -- @param #AUFTRAG self -- @param #string From From state. @@ -2493,6 +2504,7 @@ function AUFTRAG:onafterCancel(From, Event, To) -- No more repeats. self.missionRepeatMax=self.missionRepeated + self.NrepeatSuccess=self.missionRepeated -- Not necessary to delay the evaluaton?! self.dTevaluate=0 @@ -2529,6 +2541,32 @@ function AUFTRAG:onafterCancel(From, Event, To) end +--- On after "Success" event. +-- @param #AUFTRAG self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function AUFTRAG:onafterSuccess(From, Event, To) + + self.status=AUFTRAG.Status.SUCCESS + self:T(self.lid..string.format("New mission status=%s", self.status)) + + if self.missionRepeated>=self.NrepeatSuccess then + + -- Stop mission. + self:I(self.lid..string.format("Mission SUCCESS! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.missionRepeated, self.NrepeatSuccess)) + self:Stop() + + else + + -- Repeat mission. + self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.missionRepeated+1, self.NrepeatSuccess)) + self:Repeat() + + end + +end + --- On after "Failed" event. -- @param #AUFTRAG self -- @param #string From From state. @@ -2547,7 +2585,7 @@ function AUFTRAG:onafterFailed(From, Event, To) else -- Repeat mission. - self:I(self.lid..string.format("Mission failed! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.missionRepeated+1, self.missionRepeatMax)) + self:I(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.missionRepeated+1, self.missionRepeatMax)) self:Repeat() end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index cc6b775cc..9b4d64818 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -189,10 +189,10 @@ end --- Add a *scheduled* task. -- @param #NAVYGROUP self -- @param Core.Point#COORDINATE Coordinate Coordinate of the target. +-- @param #string Clock Time when to start the attack. -- @param #number Radius Radius in meters. Default 100 m. -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. --- @param #string Clock Time when to start the attack. -- @param #number Prio Priority of the task. -- @return Ops.OpsGroup#OPSGROUP.Task The task data. function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Clock, Prio) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 2d909b00b..a41229802 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -252,6 +252,7 @@ OPSGROUP.TaskType={ -- @field #boolean detour If true, this waypoint is not part of the normal route. -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. +-- @field #number npassed Number of times a groups passed this waypoint. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. -- @field Core.Point#COORDINATE roadcoord Closest point to road. -- @field #number roaddist Distance to closest point on road. @@ -1851,13 +1852,13 @@ function OPSGROUP:onbeforeMissionStart(From, Event, To, Mission) -- Activate group if it is late activated. if self:IsLateActivated() then self:Activate(delay) - --delay=delay+1 + delay=delay+1 end end -- Startup group if it is uncontrolled. - if self.isAircraft and self:IsParking() and self:IsUncontrolled() then + if self.isAircraft and self:IsUncontrolled() then self:StartUncontrolled(delay) end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 2be46ec38..3a07839fe 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -241,12 +241,12 @@ function SQUADRON:SetSkill(Skill) return self end ---- Set maintenance and repair time. +--- Set turnover and repair time. If an asset returns from a mission to the airwing, it will need some time until the asset is available for further missions. -- @param #SQUADRON self -- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 0 min. -- @param #number RepairTime Time in minutes it takes to repair a flight for each percent damage taken. Default is 0 min. -- @return #SQUADRON self -function SQUADRON:SetMaintenanceTime(MaintenanceTime, RepairTime) +function SQUADRON:SetTurnoverTime(MaintenanceTime, RepairTime) self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 self.repairtime=RepairTime and RepairTime*60 or 0 return self diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index caa714069..e47770ef4 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -764,6 +764,35 @@ function TARGET:GetTargetByName(ObjectName) end +--- Get the first target objective alive. +-- @param #TARGET self +-- @return #TARGET.Object The target objective. +function TARGET:GetObjective() + + for _,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + if target.Status==TARGET.ObjectStatus.ALIVE then + return target + end + end + + return nil +end + +--- Get the first target object alive. +-- @param #TARGET self +-- @return Wrapper.Positionable#POSITIONABLE The target object or nil. +function TARGET:GetObject() + + local target=self:GetObjective() + if target then + return target.Object + end + + return nil +end + + --- Count alive targets. -- @param #TARGET self -- @return #number Number of alive target objects. diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 947b23299..84712dc7a 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -56,6 +56,17 @@ ENUMS.ROT = { AllowAbortMission=4, } +--- Alarm state. +-- @type ENUMS.AlarmState +-- @field #number Auto AI will automatically switch alarm states based on the presence of threats. The AI kind of cheats in this regard. +-- @field #number Green Group is not combat ready. Sensors are stowed if possible. +-- @field #number Red Group is combat ready and actively searching for targets. Some groups like infantry will not move in this state. +ENUMS.AlarmState = { + Auto=0, + Green=1, + Red=2, +} + --- Weapon types. See the [Weapon Flag](https://wiki.hoggitworld.com/view/DCS_enum_weapon_flag) enumerotor on hoggit wiki. -- @type ENUMS.WeaponFlag ENUMS.WeaponFlag={ From ed47b4b3db21192a8ae12ccb19af15328837bc84 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 16 Aug 2020 00:35:42 +0200 Subject: [PATCH 37/79] Ops --- Moose Development/Moose/Core/Base.lua | 2 - Moose Development/Moose/Core/Fsm.lua | 237 ++++++++++++++---- Moose Development/Moose/Ops/AirWing.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 13 +- Moose Development/Moose/Ops/NavyGroup.lua | 12 +- Moose Development/Moose/Ops/OpsGroup.lua | 27 +- Moose Development/Moose/Ops/Squadron.lua | 9 +- .../Moose/Utilities/Profiler.lua | 133 ++++++---- 8 files changed, 316 insertions(+), 118 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 313482880..827db5778 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -26,8 +26,6 @@ -- @module Core.Base -- @image Core_Base.JPG - - local _TraceOnOff = true local _TraceLevel = 1 local _TraceAll = false diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 09d447f65..a9725c636 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -71,7 +71,7 @@ -- -- -- ### Author: **FlightControl** --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -81,6 +81,12 @@ do -- FSM --- @type FSM + -- @field #string ClassName Name of the class. + -- @field Core.Scheduler#SCHEDULER CallScheduler Call scheduler. + -- @field #table options Options. + -- @field #table subs Subs. + -- @field #table Scores Scores. + -- @field #string current Current state name. -- @extends Core.Base#BASE @@ -369,8 +375,7 @@ do -- FSM self._EventSchedules = {} self.CallScheduler = SCHEDULER:New( self ) - - + return self end @@ -379,7 +384,6 @@ do -- FSM -- @param #FSM self -- @param #string State A string defining the start state. function FSM:SetStartState( State ) - self._StartState = State self.current = State end @@ -389,7 +393,6 @@ do -- FSM -- @param #FSM self -- @return #string A string containing the start state. function FSM:GetStartState() - return self._StartState or {} end @@ -406,6 +409,7 @@ do -- FSM Transition.Event = Event Transition.To = To + -- Debug message. self:T2( Transition ) self._Transitions[Transition] = Transition @@ -414,9 +418,9 @@ do -- FSM --- Returns a table of the transition rules defined within the FSM. - -- @return #table - function FSM:GetTransitions() - + -- @param #FSM self + -- @return #table Transitions. + function FSM:GetTransitions() return self._Transitions or {} end @@ -448,7 +452,8 @@ do -- FSM --- Returns a table of the SubFSM rules defined within the FSM. - -- @return #table + -- @param #FSM self + -- @return #table Sub processes. function FSM:GetProcesses() self:F( { Processes = self._Processes } ) @@ -480,15 +485,17 @@ do -- FSM end --- Adds an End state. - function FSM:AddEndState( State ) - + -- @param #FSM self + -- @param #string State The FSM state. + function FSM:AddEndState( State ) self._EndStates[State] = State self.endstates[State] = State end --- Returns the End states. - function FSM:GetEndStates() - + -- @param #FSM self + -- @return #table End states. + function FSM:GetEndStates() return self._EndStates or {} end @@ -532,18 +539,22 @@ do -- FSM end --- Returns a table with the scores defined. - function FSM:GetScores() - + -- @param #FSM self + -- @param #table Scores. + function FSM:GetScores() return self._Scores or {} end --- Returns a table with the Subs defined. - function FSM:GetSubs() - + -- @param #FSM self + -- @return #table Sub processes. + function FSM:GetSubs() return self.options.subs end - + --- Load call backs. + -- @param #FSM self + -- @param #table CallBackTable Table of call backs. function FSM:LoadCallBacks( CallBackTable ) for name, callback in pairs( CallBackTable or {} ) do @@ -551,21 +562,34 @@ do -- FSM end end - + + --- Event map. + -- @param #FSM self + -- @param #table Events Events. + -- @param #table EventStructure Event structure. function FSM:_eventmap( Events, EventStructure ) local Event = EventStructure.Event local __Event = "__" .. EventStructure.Event + self[Event] = self[Event] or self:_create_transition(Event) self[__Event] = self[__Event] or self:_delayed_transition(Event) + + -- Debug message. self:T2( "Added methods: " .. Event .. ", " .. __Event ) + Events[Event] = self.Events[Event] or { map = {} } self:_add_to_map( Events[Event].map, EventStructure ) end - + + --- Sub maps. + -- @param #FSM self + -- @param #table subs Subs. + -- @param #table sub Sub. + -- @param #string name Name. function FSM:_submap( subs, sub, name ) - --self:F( { sub = sub, name = name } ) + subs[sub.From] = subs[sub.From] or {} subs[sub.From][sub.Event] = subs[sub.From][sub.Event] or {} @@ -578,22 +602,23 @@ do -- FSM subs[sub.From][sub.Event][sub].ReturnEvents = sub.ReturnEvents or {} -- these events need to be given to find the correct continue event ... if none given, the processing will stop. subs[sub.From][sub.Event][sub].name = name subs[sub.From][sub.Event][sub].fsmparent = self + end - + --- Call handler. + -- @param #FSM self + -- @param #string step Step "onafter", "onbefore", "onenter", "onleave". + -- @param #string trigger Trigger. + -- @param #table params Parameters. + -- @param #string EventName Event name. + -- @return Value. function FSM:_call_handler( step, trigger, params, EventName ) + --env.info(string.format("FF T=%.3f _call_handler step=%s, trigger=%s, event=%s", timer.getTime(), step, trigger, EventName)) local handler = step .. trigger - local ErrorHandler = function( errmsg ) - - env.info( "Error in SCHEDULER function:" .. errmsg ) - if BASE.Debug ~= nil then - env.info( BASE.Debug.traceback() ) - end - - return errmsg - end + if self[handler] then + if step == "onafter" or step == "OnAfter" then self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] ) elseif step == "onbefore" or step == "OnBefore" then @@ -604,14 +629,31 @@ do -- FSM self:T( ":::>" .. step .. params[1] .. " : " .. params[1] .. ">" .. step .. params[1] .. "()" .. " >> " .. params[2] .. " >> " .. params[3] ) else self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] ) - end + end + self._EventSchedules[EventName] = nil + + -- Error handler. + local ErrorHandler = function( errmsg ) + env.info( "Error in SCHEDULER function:" .. errmsg ) + if BASE.Debug ~= nil then + env.info( BASE.Debug.traceback() ) + end + return errmsg + end + + -- Protected call. local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + return Value end + end - --- @param #FSM self + --- Handler. + -- @param #FSM self + -- @param #string EventName Event name. + -- @param ... Arguments. function FSM._handler( self, EventName, ... ) local Can, To = self:can( EventName ) @@ -621,7 +663,11 @@ do -- FSM end if Can then + + -- From state. local From = self.current + + -- Parameters. local Params = { From, EventName, To, ... } @@ -632,8 +678,8 @@ do -- FSM self["onafter".. EventName] or self["OnAfter".. EventName] or self["onenter".. To] or - self["OnEnter".. To] - then + self["OnEnter".. To] then + if self:_call_handler( "onbefore", EventName, Params, EventName ) == false then self:T( "*** FSM *** Cancel" .. " *** " .. self.current .. " --> " .. EventName .. " --> " .. To .. " *** onbefore" .. EventName ) return false @@ -653,8 +699,11 @@ do -- FSM end end end + else + local ClassName = self:GetClassName() + if ClassName == "FSM" then self:T( "*** FSM *** Transit *** " .. self.current .. " --> " .. EventName .. " --> " .. To ) end @@ -672,84 +721,125 @@ do -- FSM end end + -- New current state. self.current = To local execute = true local subtable = self:_gosub( From, EventName ) + for _, sub in pairs( subtable ) do + --if sub.nextevent then -- self:F2( "nextevent = " .. sub.nextevent ) -- self[sub.nextevent]( self ) --end + self:T( "*** FSM *** Sub *** " .. sub.StartEvent ) + sub.fsm.fsmparent = self sub.fsm.ReturnEvents = sub.ReturnEvents sub.fsm[sub.StartEvent]( sub.fsm ) + execute = false end local fsmparent, Event = self:_isendstate( To ) + if fsmparent and Event then + self:T( "*** FSM *** End *** " .. Event ) + self:_call_handler("onenter", To, Params, EventName ) self:_call_handler("OnEnter", To, Params, EventName ) self:_call_handler("onafter", EventName, Params, EventName ) self:_call_handler("OnAfter", EventName, Params, EventName ) self:_call_handler("onstate", "change", Params, EventName ) + fsmparent[Event]( fsmparent ) + execute = false end if execute then - self:_call_handler("onafter", EventName, Params, EventName ) - self:_call_handler("OnAfter", EventName, Params, EventName ) - -- only execute the call if the From state is not equal to the To state! Otherwise this function should never execute! - --if from ~= to then - self:_call_handler("onenter", To, Params, EventName ) - self:_call_handler("OnEnter", To, Params, EventName ) - --end + self:_call_handler("onafter", EventName, Params, EventName ) + self:_call_handler("OnAfter", EventName, Params, EventName ) + + self:_call_handler("onenter", To, Params, EventName ) + self:_call_handler("OnEnter", To, Params, EventName ) self:_call_handler("onstate", "change", Params, EventName ) + end else - self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) + self:I( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) end return nil end - + + --- Delayed transition. + -- @param #FSM self + -- @param #string EventName Event name. + -- @return #function Function. function FSM:_delayed_transition( EventName ) + return function( self, DelaySeconds, ... ) + + -- Debug. self:T2( "Delayed Event: " .. EventName ) + local CallID = 0 if DelaySeconds ~= nil then + if DelaySeconds < 0 then -- Only call the event ONCE! + DelaySeconds = math.abs( DelaySeconds ) - if not self._EventSchedules[EventName] then + + if not self._EventSchedules[EventName] then + + -- Call _handler. CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) + + -- Set call ID. self._EventSchedules[EventName] = CallID + + -- Debug output. self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) else self:T2(string.format("NEGATIVE Event %s delayed by %.1f sec CANCELLED as we already have such an event in the queue.", EventName, DelaySeconds)) -- reschedule end else + CallID = self.CallScheduler:Schedule( self, self._handler, { EventName, ... }, DelaySeconds or 1, nil, nil, nil, 4, true ) + self:T2(string.format("Event %s delayed by %.1f sec SCHEDULED with CallID=%s", EventName, DelaySeconds, tostring(CallID))) end else error( "FSM: An asynchronous event trigger requires a DelaySeconds parameter!!! This can be positive or negative! Sorry, but will not process this." ) end + + -- Debug. self:T2( { CallID = CallID } ) end + end - + + --- Create transition. + -- @param #FSM self + -- @param #string EventName Event name. + -- @return #function Function. function FSM:_create_transition( EventName ) return function( self, ... ) return self._handler( self, EventName , ... ) end end - + + --- Go sub. + -- @param #FSM self + -- @param #string ParentFrom Parent from state. + -- @param #string ParentEvent Parent event name. + -- @return #table Subs. function FSM:_gosub( ParentFrom, ParentEvent ) local fsmtable = {} if self.subs[ParentFrom] and self.subs[ParentFrom][ParentEvent] then @@ -759,9 +849,15 @@ do -- FSM return {} end end - + + --- Is end state. + -- @param #FSM self + -- @param #string Current Current state name. + -- @return #table FSM parent. + -- @return #string Event name. function FSM:_isendstate( Current ) local FSMParent = self.fsmparent + if FSMParent and self.endstates[Current] then --self:T( { state = Current, endstates = self.endstates, endstate = self.endstates[Current] } ) FSMParent.current = Current @@ -778,9 +874,14 @@ do -- FSM return nil end - + + --- Add to map. + -- @param #FSM self + -- @param #table Map Map. + -- @param #table Event Event table. function FSM:_add_to_map( Map, Event ) self:F3( { Map, Event } ) + if type(Event.From) == 'string' then Map[Event.From] = Event.To else @@ -788,33 +889,59 @@ do -- FSM Map[From] = Event.To end end + self:T3( { Map, Event } ) end - + + --- Get current state. + -- @param #FSM self + -- @return #string Current FSM state. function FSM:GetState() return self.current end - + + --- Get current state. + -- @param #FSM self + -- @return #string Current FSM state. function FSM:GetCurrentState() return self.current end - + --- Check if FSM is in state. + -- @param #FSM self + -- @param #string State State name. + -- @param #boolean If true, FSM is in this state. function FSM:Is( State ) return self.current == State end - + + --- Check if FSM is in state. + -- @param #FSM self + -- @param #string State State name. + -- @param #boolean If true, FSM is in this state. function FSM:is(state) return self.current == state end - + + --- Check if can do an event. + -- @param #FSM self + -- @param #string e Event name. + -- @return #boolean If true, FSM can do the event. + -- @return #string To state. function FSM:can(e) local Event = self.Events[e] + self:F3( { self.current, Event } ) + local To = Event and Event.map[self.current] or Event.map['*'] + return To ~= nil, To end - + + --- Check if cannot do an event. + -- @param #FSM self + -- @param #string e Event name. + -- @return #boolean If true, FSM cannot do the event. function FSM:cannot(e) return not self:can(e) end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 40e65adad..0395508a3 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1988,6 +1988,7 @@ function AIRWING:CountAssetsOnMission(MissionTypes, Squadron) end end + env.info(string.format("FF N=%d Np=%d, Nq=%d", Np+Nq, Np, Nq)) return Np+Nq, Np, Nq end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f808174ec..698028882 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -486,6 +486,7 @@ function AUFTRAG:New(Type) self.engageAsGroup=true self.missionRepeated=0 self.missionRepeatMax=0 + self.NrepeatSuccess=0 self.nassets=1 self.dTevaluate=0 self.Ncasualties=0 @@ -982,6 +983,10 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) if type(Airdrome)=="string" then Airdrome=AIRBASE:FindByName(Airdrome) end + + if Airdrome:IsInstanceOf("AIRBASE") then + + end local mission=AUFTRAG:New(AUFTRAG.Type.BOMBRUNWAY) @@ -995,7 +1000,7 @@ function AUFTRAG:NewBOMBRUNWAY(Airdrome, Altitude) -- Mission options: mission.missionTask=ENUMS.MissionTask.RUNWAYATTACK mission.missionAltitude=mission.engageAltitude*0.8 - mission.missionFraction=0.2 + mission.missionFraction=0.75 mission.optionROE=ENUMS.ROE.OpenFire mission.optionROT=ENUMS.ROT.PassiveDefense @@ -3052,7 +3057,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- BOMBRUNWAY Mission -- ------------------------ - local DCStask=CONTROLLABLE.TaskBombingRunway(nil, self.engageTarget.Target, self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAsGroup) + local DCStask=CONTROLLABLE.TaskBombingRunway(nil, self.engageTarget:GetObject(), self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAsGroup) table.insert(DCStasks, DCStask) @@ -3092,7 +3097,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- ESCORT Mission -- -------------------- - local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget.Target, self.escortVec3, LastWaypointIndex, self.engageMaxDistance, self.engageTargetTypes) + local DCStask=CONTROLLABLE.TaskEscort(nil, self.engageTarget:GetObject(), self.escortVec3, LastWaypointIndex, self.engageMaxDistance, self.engageTargetTypes) table.insert(DCStasks, DCStask) @@ -3102,7 +3107,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- FAC Mission -- ----------------- - local DCStask=CONTROLLABLE.TaskFAC_AttackGroup(nil, self.engageTarget.Target, self.engageWeaponType, self.facDesignation, self.facDatalink, self.facFreq, self.facModu, CallsignName, CallsignNumber) + local DCStask=CONTROLLABLE.TaskFAC_AttackGroup(nil, self.engageTarget:GetObject(), self.engageWeaponType, self.facDesignation, self.facDatalink, self.facFreq, self.facModu, CallsignName, CallsignNumber) table.insert(DCStasks, DCStask) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9b4d64818..dbb1896d7 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -416,6 +416,9 @@ function NAVYGROUP:onafterStatus(From, Event, To) local pos=self:GetCoordinate() local speed=self.group:GetVelocityKNOTS() + -- Update last known position, orientation, velocity. + self:_UpdatePosition() + -- Check if group started or stopped turning. self:_CheckTurning() @@ -440,9 +443,6 @@ function NAVYGROUP:onafterStatus(From, Event, To) end - -- Check water is ahead. - --collision=self:_CheckCollisionCoord(pos:Translate(freepath+100, hdg)) - end -- Check into wind queue. @@ -1348,10 +1348,10 @@ function NAVYGROUP:_CheckTurning() if unit and unit:IsAlive() then -- Current orientation of carrier. - local vNew=unit:GetOrientationX() + local vNew=self.orientX --unit:GetOrientationX() -- Last orientation from 30 seconds ago. - local vLast=self.Corientlast or vNew + local vLast=self.orientXLast --self.Corientlast or vNew -- We only need the X-Z plane. vNew.y=0 ; vLast.y=0 @@ -1360,7 +1360,7 @@ function NAVYGROUP:_CheckTurning() local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) -- Last orientation becomes new orientation - self.Corientlast=vNew + --self.Corientlast=vNew -- Carrier is turning when its heading changed by at least two degrees since last check. local turning=math.abs(deltaLast)>=2 diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a41229802..589891d63 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2386,7 +2386,7 @@ function OPSGROUP:onafterCheckZone(From, Event, To) end if not self:IsStopped() then - self:__CheckZone(-1) + self:__CheckZone(-10) end end @@ -2443,7 +2443,6 @@ function OPSGROUP:_CheckInZones() self:EnterZone(enterzone) end - end end @@ -3494,6 +3493,30 @@ end -- Element and Group Status Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Check if all elements of the group have the same status (or are dead). +-- @param #OPSGROUP self +-- @param #string unitname Name of unit. +function OPSGROUP:_UpdatePosition() + + if self:IsAlive() then + + self.positionLast=self.position or self:GetCoordinate() + self.headingLast=self.heading or self:GetHeading() + self.orientXLast=self.orientX or self.group:GetUnit(1):GetOrientationX() + self.velocityLast=self.velocity or self.group:GetVelocityMPS() + + self.position=self:GetCoordinate() + self.heading=self:GetHeading() + self.orientX=self.group:GetUnit(1):GetOrientationX() + self.velocity=self.group:GetVelocityMPS() + + self.dTpositionUpdate=self.TpositionUpdate and self.TpositionUpdate-timer.getAbsTime() or 0 + self.TpositionUpdate=timer.getAbsTime() + + end + +end + --- Check if all elements of the group have the same status (or are dead). -- @param #OPSGROUP self -- @param #string unitname Name of unit. diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 3a07839fe..8fe221598 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -574,8 +574,11 @@ function SQUADRON:onafterStatus(From, Event, To) local NassetsTot=#self.assets local NassetsInS=self:CountAssetsInStock() - local NassetsQP, NassetsP, NassetsQ=self.airwing and self.airwing:CountAssetsOnMission(nil, self) or 0,0,0 - + local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 + if self.airwing then + NassetsQP, NassetsP, NassetsQ=self.airwing:CountAssetsOnMission(nil, self) + end + -- Short info. local text=string.format("%s [Type=%s, Callsign=%s, Modex=%d, Skill=%s]: Assets Total=%d, InStock=%d, OnMission=%d [P=%d, Q=%d]", fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ) @@ -613,6 +616,8 @@ function SQUADRON:_CheckAssetStatus() if mission then local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 text=text..string.format(" Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) + else + text=text.."Mission None" end -- Flight status. diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 7dabbb068..a7803975b 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -74,16 +74,22 @@ PROFILER = { fTimeTotal = {}, eventHandler = {}, startTime = nil, - endTime = nil, runTime = nil, - sortBy = 1, logUnknown = false, lowCpsThres = 5, + fileName = "", } -PROFILER.sortBy=1 -- Sort reports by 0=Count, 1=Total time by function -PROFILER.logUnknown=true -- Log unknown functions -PROFILER.lowCpsThres=1 -- Skip results with less than X calls per second +--- Waypoint data. +-- @type PROFILER.Data +-- @field #string func The function name. +-- @field #string src The source file. +-- @field #number line The line number +-- @field #number count Number of function calls. +-- @field #number tm Total time in seconds. + +PROFILER.logUnknown=false -- Log unknown functions +PROFILER.lowCpsThres=5 -- Skip results with less than X calls per second PROFILER.fileName="_LuaProfiler.txt" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -99,9 +105,8 @@ function PROFILER.Start(Delay, Duration) BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) else - PROFILER.startTime=timer.getTime() - PROFILER.endTime=0 - PROFILER.runTime=0 + PROFILER.TstartGame=timer.getTime() + PROFILER.TstartOS=os.clock() -- Add event handler. world.addEventHandler(PROFILER.eventHandler) @@ -142,11 +147,10 @@ function PROFILER.Stop(Delay) -- Remove hook. debug.sethook() - -- Set end time. - PROFILER.endTime=timer.getTime() -- Run time. - PROFILER.runTime=PROFILER.endTime-PROFILER.startTime + PROFILER.runTimeGame=timer.getTime()-PROFILER.TstartGame + PROFILER.runTimeOS=os.clock()-PROFILER.TstartOS -- Show info. PROFILER.showInfo() @@ -208,13 +212,19 @@ end --- Get data. -- @param #function func Function. --- @param #boolean detailed Not used. -function PROFILER.getData(func, detailed) +-- @return #string Function name. +-- @return #string Source file name. +-- @return #string Line number. +-- @return #number Function time in seconds. +function PROFILER.getData(func) + local n=PROFILER.dInfo[func] + if n.what=="C" then - return n.name, "", "", PROFILER.fTimeTotal[func] + return n.name, "?", "?", PROFILER.fTimeTotal[func] end - return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func] + + return n.name, n.short_src, n.linedefined, PROFILER.fTimeTotal[func] end --- Write text to log file. @@ -225,24 +235,27 @@ function PROFILER._flog(f, txt) end --- Show table. --- @param #table t Data table. +-- @param #table data Data table. -- @param #function f The file. -- @param #boolean detailed Show detailed info. -function PROFILER.showTable(t, f, detailed) - for i=1, #t do +function PROFILER.showTable(data, f, detailed) + + -- Loop over data. + for i=1, #data do + local t=data[i] --#PROFILER.Data - local cps=t[i].count/PROFILER.runTime + -- Calls per second. + local cps=t.count/PROFILER.runTimeGame if (cps>=PROFILER.lowCpsThres) then - if (detailed==false) then - PROFILER._flog(f,"- Function: "..t[i].func..": "..tostring(t[i].count).." ("..string.format("%.01f",cps).."/sec) Time: "..string.format("%g",t[i].tm).." seconds") - else - PROFILER._flog(f,"- Function: "..t[i].func..": "..tostring(t[i].count).." ("..string.format("%.01f",cps).."/sec) "..tostring(t[i].src)..":"..tostring(t[i].line).." Time: "..string.format("%g",t[i].tm).." seconds") - end + -- Output + local text=string.format("%20s: %8d calls %8.1f/sec - Time %8.3f sec (%.3f %%) %s line %s", t.func, t.count, cps, t.tm, t.tm/PROFILER.runTimeGame*100, tostring(t.src), tostring(t.line)) + PROFILER._flog(f, text) end end + end --- Write info to output file. @@ -250,15 +263,15 @@ function PROFILER.showInfo() -- Output file. local file=lfs.writedir()..[[Logs\]]..PROFILER.fileName - local f=io.open(file, 'w') - - BASE:I(string.format("### Profiler: Writing result to file %s", file)) + local f=io.open(file, 'w') -- Gather data. + local Ttot=0 + local Calls=0 local t={} for func, count in pairs(PROFILER.Counters) do - local s,src,line,tm=PROFILER.getData(func, false) + local s,src,line,tm=PROFILER.getData(func) if PROFILER.logUnknown==true then if s==nil then s="" end @@ -272,39 +285,65 @@ function PROFILER.showInfo() count=count, tm=tm, } + Ttot=Ttot+tm + Calls=Calls+count end end - - -- Sort result. - if PROFILER.sortBy==0 then - table.sort(t, function(a,b) return a.count>b.count end ) - end - if (PROFILER.sortBy==1) then - table.sort(t, function(a,b) return a.tm>b.tm end ) - end + + env.info("**************************************************************************************************") + env.info(string.format("Profiler")) + env.info(string.format("--------")) + env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(PROFILER.runTimeGame, true), PROFILER.runTimeGame)) + env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(PROFILER.runTimeOS, true), PROFILER.runTimeOS)) + env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/PROFILER.runTimeGame*100)) + env.info(string.format("* Total functions : %d", #t)) + env.info(string.format("* Total func calls : %d", Calls)) + env.info(string.format("* Writing to file : \"%s\"", file)) + env.info("**************************************************************************************************") + + -- Sort by total time. + table.sort(t, function(a,b) return a.tm>b.tm end) -- Write data. PROFILER._flog(f,"") - PROFILER._flog(f,"#### #### #### #### #### ##### #### #### #### #### ####") - PROFILER._flog(f,"#### #### #### ---- Profiler Report ---- #### #### ####") - PROFILER._flog(f,"#### Profiler Runtime: "..string.format("%.01f",PROFILER.runTime/60).." minutes") - PROFILER._flog(f,"#### #### #### #### #### ##### #### #### #### #### ####") - PROFILER._flog(f,"") - PROFILER.showTable(t, f, false) + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER._flog(f,"-------------------------") + PROFILER._flog(f,"---- Profiler Report ----") + PROFILER._flog(f,"-------------------------") + PROFILER._flog(f,"") + PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(PROFILER.runTimeGame, true), PROFILER.runTimeGame)) + PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(PROFILER.runTimeOS, true), PROFILER.runTimeOS).." (can vary significantly compared to the game time)") + PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/PROFILER.runTimeGame*100)) + PROFILER._flog(f,"") + PROFILER._flog(f,string.format("* Total functions = %d", #t)) + PROFILER._flog(f,string.format("* Total func calls = %d", Calls)) + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, true) + + -- Sort by number of calls. + table.sort(t, function(a,b) return a.count>b.count end) -- Detailed data. PROFILER._flog(f,"") - PROFILER._flog(f,"#### #### #### #### #### #### #### #### #### #### #### #### ####") - PROFILER._flog(f,"#### #### #### ---- Profiler Detailed Report ---- #### #### ####") - PROFILER._flog(f,"#### #### #### #### #### #### #### #### #### #### #### #### ####") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER._flog(f,"------------------------------") + PROFILER._flog(f,"---- Data Sorted by Calls ----") + PROFILER._flog(f,"------------------------------") PROFILER._flog(f,"") PROFILER.showTable(t, f, true) -- Closing. PROFILER._flog(f,"") - PROFILER._flog(f,"#### #### #### #### #### #### #### #### #### #### #### #### ####") - + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"************************************************************************************************************************") -- Close file. f:close() end From 3ea8b3737f4b6da47884163cdc9ba9811414c82f Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 16 Aug 2020 01:18:18 +0200 Subject: [PATCH 38/79] Update Profiler.lua --- Moose Development/Moose/Utilities/Profiler.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index a7803975b..1ab6368ff 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -89,7 +89,7 @@ PROFILER = { -- @field #number tm Total time in seconds. PROFILER.logUnknown=false -- Log unknown functions -PROFILER.lowCpsThres=5 -- Skip results with less than X calls per second +PROFILER.lowCpsThres=0 -- Skip results with less than X calls per second PROFILER.fileName="_LuaProfiler.txt" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -250,7 +250,7 @@ function PROFILER.showTable(data, f, detailed) if (cps>=PROFILER.lowCpsThres) then -- Output - local text=string.format("%20s: %8d calls %8.1f/sec - Time %8.3f sec (%.3f %%) %s line %s", t.func, t.count, cps, t.tm, t.tm/PROFILER.runTimeGame*100, tostring(t.src), tostring(t.line)) + local text=string.format("%30s: %8d calls %8.1f/sec - Time %8.3f sec (%.3f %%) %s line %s", t.func, t.count, cps, t.tm, t.tm/PROFILER.runTimeGame*100, tostring(t.src), tostring(t.line)) PROFILER._flog(f, text) end @@ -276,8 +276,8 @@ function PROFILER.showInfo() if PROFILER.logUnknown==true then if s==nil then s="" end end - - if (s~=nil) then + + if s~=nil and s~="_copy" and s~="_Serialize" and s~="(for generator)" and s~="pairs" then t[#t+1]= { func=s, src=src, From 2a4f6020c2e467a21e65b0b04e0bae3405cdf3d4 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 17 Aug 2020 01:24:51 +0200 Subject: [PATCH 39/79] Ops --- .../Moose/Functional/Warehouse.lua | 194 +++++++-------- Moose Development/Moose/Ops/AirWing.lua | 117 ++++------ Moose Development/Moose/Ops/Auftrag.lua | 68 ++++-- Moose Development/Moose/Ops/FlightGroup.lua | 20 +- Moose Development/Moose/Ops/NavyGroup.lua | 54 ++--- Moose Development/Moose/Ops/OpsGroup.lua | 56 +++-- Moose Development/Moose/Ops/Squadron.lua | 76 +++--- Moose Development/Moose/Ops/Target.lua | 53 +++++ .../Moose/Utilities/Profiler.lua | 220 ++++++++++++++---- Moose Development/Moose/Wrapper/Marker.lua | 4 +- 10 files changed, 535 insertions(+), 327 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 213805a8d..6f228c997 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -46,6 +46,7 @@ -- @type WAREHOUSE -- @field #string ClassName Name of the class. -- @field #boolean Debug If true, send debug messages to all. +-- @field #number verbose Verbosity level. -- @field #string wid Identifier of the warehouse printed before other output to DCS.log file. -- @field #boolean Report If true, send status messages to coalition. -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. @@ -1830,14 +1831,12 @@ WAREHOUSE.version="1.0.2" -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) - BASE:T({warehouse=warehouse}) -- Check if just a string was given and convert to static. if type(warehouse)=="string" then local warehousename=warehouse warehouse=UNIT:FindByName(warehousename) if warehouse==nil then - --env.info(string.format("No warehouse unit with name %s found trying static.", tostring(warehousename))) warehouse=STATIC:FindByName(warehousename, true) self.isunit=false else @@ -1858,7 +1857,7 @@ function WAREHOUSE:New(warehouse, alias) env.info(string.format("Adding warehouse v%s for structure %s with alias %s", WAREHOUSE.version, warehouse:GetName(), self.alias)) -- Inherit everthing from FSM class. - local self = BASE:Inherit(self, FSM:New()) -- #WAREHOUSE + local self=BASE:Inherit(self, FSM:New()) -- #WAREHOUSE -- Set some string id for output to DCS.log file. self.lid=string.format("WAREHOUSE %s | ", self.alias) @@ -1890,6 +1889,8 @@ function WAREHOUSE:New(warehouse, alias) -- Defaults self:SetMarker(true) + self:SetReportOff() + self:SetVerbosity(0) -- Add warehouse to database. _WAREHOUSEDB.Warehouses[self.uid]=self @@ -2551,6 +2552,15 @@ function WAREHOUSE:SetStatusUpdate(timeinterval) return self end +--- Set verbosity level. +-- @param #WAREHOUSE self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #WAREHOUSE self +function WAREHOUSE:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + --- Set a zone where the (ground) assets of the warehouse are spawned once requested. -- @param #WAREHOUSE self -- @param Core.Zone#ZONE zone The spawn zone. @@ -3253,16 +3263,6 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Save self in static object. Easier to retrieve later. self.warehouse:SetState(self.warehouse, "WAREHOUSE", self) - -- THIS! caused aircraft to be spawned and started but they would never begin their route! - -- VERY strange. Need to test more. - --[[ - -- Debug mark warehouse & spawn zone. - self.zone:BoundZone(30, self.country) - self.spawnzone:BoundZone(30, self.country) - ]] - - --self.spawnzone:GetCoordinate():MarkToCoalition(string.format("Warehouse %s spawn zone", self.alias), self:GetCoalition()) - -- Get the closest point on road wrt spawnzone of ground assets. local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad() if _road and self.road==nil then @@ -3402,13 +3402,17 @@ end -- @param #string To To state. function WAREHOUSE:onafterStatus(From, Event, To) - local FSMstate=self:GetState() - - local coalition=self:GetCoalitionName() - local country=self:GetCountryName() - - -- Info. - self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d, Requests: waiting=%d, pending=%d", FSMstate, country, coalition, #self.stock, #self.queue, #self.pending)) + -- General info. + if self.verbose>=1 then + + local FSMstate=self:GetState() + + local coalition=self:GetCoalitionName() + local country=self:GetCountryName() + + -- Info. + self:I(self.lid..string.format("State=%s %s [%s]: Assets=%d, Requests: waiting=%d, pending=%d", FSMstate, country, coalition, #self.stock, #self.queue, #self.pending)) + end -- Check if any pending jobs are done and can be deleted from the queue. self:_JobDone() @@ -7616,10 +7620,10 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local problem=nil -- Safe parking using TO_AC from DCS result. - self:I(self.lid..string.format("Parking spot %d TOAC=%s (safe park=%s).", _termid, tostring(_toac), tostring(self.safeparking))) + self:T2(self.lid..string.format("Parking spot %d TOAC=%s (safe park=%s)", _termid, tostring(_toac), tostring(self.safeparking))) if self.safeparking and _toac then free=false - self:I(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).", _termid)) + self:T(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC)", _termid)) end -- Loop over all obstacles. @@ -7648,7 +7652,8 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Add parkingspot for this asset unit. table.insert(parking[_asset.uid], parkingspot) - self:I(self.lid..string.format("Parking spot %d is free for asset id=%d!", _termid, _asset.uid)) + -- Debug + self:T(self.lid..string.format("Parking spot %d is free for asset id=%d!", _termid, _asset.uid)) -- Add the unit as obstacle so that this spot will not be available for the next unit. table.insert(obstacles, {coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"}) @@ -7659,7 +7664,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) else -- Debug output for occupied spots. - self:I(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) + self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) if self.Debug then local coord=problem.coord --Core.Point#COORDINATE local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) @@ -8284,67 +8289,70 @@ end -- @param #string name Name of the queue for info reasons. function WAREHOUSE:_PrintQueue(queue, name) - local total="Empty" - if #queue>0 then - total=string.format("Total = %d", #queue) - end + if self.verbose>=2 then - -- Init string. - local text=string.format("%s at %s: %s",name, self.alias, total) - - for i,qitem in ipairs(queue) do - local qitem=qitem --#WAREHOUSE.Pendingitem - - local uid=qitem.uid - local prio=qitem.prio - local clock="N/A" - if qitem.timestamp then - clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) + local total="Empty" + if #queue>0 then + total=string.format("Total = %d", #queue) end - local assignment=tostring(qitem.assignment) - local requestor=qitem.warehouse.alias - local airbasename=qitem.warehouse:GetAirbaseName() - local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory() - local assetdesc=qitem.assetdesc - local assetdescval=qitem.assetdescval - if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then - assetdescval="Asset list" + + -- Init string. + local text=string.format("%s at %s: %s",name, self.alias, total) + + for i,qitem in ipairs(queue) do + local qitem=qitem --#WAREHOUSE.Pendingitem + + local uid=qitem.uid + local prio=qitem.prio + local clock="N/A" + if qitem.timestamp then + clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) + end + local assignment=tostring(qitem.assignment) + local requestor=qitem.warehouse.alias + local airbasename=qitem.warehouse:GetAirbaseName() + local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory() + local assetdesc=qitem.assetdesc + local assetdescval=qitem.assetdescval + if assetdesc==WAREHOUSE.Descriptor.ASSETLIST then + assetdescval="Asset list" + end + local nasset=tostring(qitem.nasset) + local ndelivered=tostring(qitem.ndelivered) + local ncargogroupset="N/A" + if qitem.cargogroupset then + ncargogroupset=tostring(qitem.cargogroupset:Count()) + end + local transporttype="N/A" + if qitem.transporttype then + transporttype=qitem.transporttype + end + local ntransport="N/A" + if qitem.ntransport then + ntransport=tostring(qitem.ntransport) + end + local ntransportalive="N/A" + if qitem.transportgroupset then + ntransportalive=tostring(qitem.transportgroupset:Count()) + end + local ntransporthome="N/A" + if qitem.ntransporthome then + ntransporthome=tostring(qitem.ntransporthome) + end + + -- Output text: + text=text..string.format( + "\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s", + i, uid, prio, clock, assignment, requestor, airbasename, requestorAirbaseCat, assetdesc, assetdescval, nasset, ncargogroupset, ndelivered, transporttype, ntransport, ntransportalive, ntransporthome) + end - local nasset=tostring(qitem.nasset) - local ndelivered=tostring(qitem.ndelivered) - local ncargogroupset="N/A" - if qitem.cargogroupset then - ncargogroupset=tostring(qitem.cargogroupset:Count()) - end - local transporttype="N/A" - if qitem.transporttype then - transporttype=qitem.transporttype - end - local ntransport="N/A" - if qitem.ntransport then - ntransport=tostring(qitem.ntransport) - end - local ntransportalive="N/A" - if qitem.transportgroupset then - ntransportalive=tostring(qitem.transportgroupset:Count()) - end - local ntransporthome="N/A" - if qitem.ntransporthome then - ntransporthome=tostring(qitem.ntransporthome) - end - - -- Output text: - text=text..string.format( - "\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s", - i, uid, prio, clock, assignment, requestor, airbasename, requestorAirbaseCat, assetdesc, assetdescval, nasset, ncargogroupset, ndelivered, transporttype, ntransport, ntransportalive, ntransporthome) - - end - - if #queue==0 then - self:T(self.lid..text) - else - if total~="Empty" then + + if #queue==0 then self:I(self.lid..text) + else + if total~="Empty" then + self:I(self.lid..text) + end end end end @@ -8352,17 +8360,19 @@ end --- Display status of warehouse. -- @param #WAREHOUSE self function WAREHOUSE:_DisplayStatus() - local text=string.format("\n------------------------------------------------------\n") - text=text..string.format("Warehouse %s status: %s\n", self.alias, self:GetState()) - text=text..string.format("------------------------------------------------------\n") - text=text..string.format("Coalition name = %s\n", self:GetCoalitionName()) - text=text..string.format("Country name = %s\n", self:GetCountryName()) - text=text..string.format("Airbase name = %s (category=%d)\n", self:GetAirbaseName(), self:GetAirbaseCategory()) - text=text..string.format("Queued requests = %d\n", #self.queue) - text=text..string.format("Pending requests = %d\n", #self.pending) - text=text..string.format("------------------------------------------------------\n") - text=text..self:_GetStockAssetsText() - self:T(text) + if self.verbose>=3 then + local text=string.format("\n------------------------------------------------------\n") + text=text..string.format("Warehouse %s status: %s\n", self.alias, self:GetState()) + text=text..string.format("------------------------------------------------------\n") + text=text..string.format("Coalition name = %s\n", self:GetCoalitionName()) + text=text..string.format("Country name = %s\n", self:GetCountryName()) + text=text..string.format("Airbase name = %s (category=%d)\n", self:GetAirbaseName(), self:GetAirbaseCategory()) + text=text..string.format("Queued requests = %d\n", #self.queue) + text=text..string.format("Pending requests = %d\n", #self.pending) + text=text..string.format("------------------------------------------------------\n") + text=text..self:_GetStockAssetsText() + self:I(text) + end end --- Get text about warehouse stock. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 0395508a3..e2633f33c 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -204,6 +204,7 @@ function AIRWING:New(warehousename, airwingname) self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. -- Defaults: + self:SetVerbosity(0) self.nflightsCAP=0 self.nflightsAWACS=0 self.nflightsTANKERboom=0 @@ -559,6 +560,15 @@ function AIRWING:GetSquadron(SquadronName) return nil end +--- Set verbosity level. +-- @param #AIRWING self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #AIRWING self +function AIRWING:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + --- Get squadron of an asset. -- @param #AIRWING self -- @param #AIRWING.SquadronAsset Asset The squadron asset. @@ -792,93 +802,52 @@ function AIRWING:onafterStatus(From, Event, To) -- Check Rescue Helo missions. self:CheckRescuhelo() - -- Count missions not over yet. - local nmissions=self:CountMissionsInQueue() - - -- Count ALL payloads in stock. If any payload is unlimited, this gives 999. - local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type) -- General info: - -- TODO: assets total - local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d", fsmstate, nmissions, Npayloads, #self.payloads, #self.squadrons) - self:I(self.lid..text) + if self.verbose>=1 then + + -- Count missions not over yet. + local nmissions=self:CountMissionsInQueue() + + -- Count ALL payloads in stock. If any payload is unlimited, this gives 999. + local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type) + + -- TODO: assets total + + -- Output. + local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d", fsmstate, nmissions, Npayloads, #self.payloads, #self.squadrons) + self:I(self.lid..text) + end ------------------ -- Mission Info -- ------------------ - local text=string.format("Missions Total=%d:", #self.missionqueue) - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - text=text..string.format("\n[%d] %s: Status=%s, Nassets=%d, Prio=%d, ID=%d (%s)", i, mission.type, mission.status, mission.nassets, mission.prio, mission.auftragsnummer, mission.name) + if self.verbose>=2 then + local text=string.format("Missions Total=%d:", #self.missionqueue) + for i,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + text=text..string.format("\n[%d] %s: Status=%s, Nassets=%d, Prio=%d, ID=%d (%s)", i, mission.type, mission.status, mission.nassets, mission.prio, mission.auftragsnummer, mission.name) + end + self:I(self.lid..text) end - self:I(self.lid..text) - + ------------------- -- Squadron Info -- ------------------- - local text="Squadrons:" - for i,_squadron in pairs(self.squadrons) do - local squadron=_squadron --Ops.Squadron#SQUADRON - - local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" - local modex=squadron.modex and squadron.modex or -1 - local skill=squadron.skill and tostring(squadron.skill) or "N/A" - - -- Squadron text - text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) - - -- Loop over all assets. - if self.verbose>1 then - for j,_asset in pairs(squadron.assets) do - local asset=_asset --#AIRWING.SquadronAsset - local assignment=asset.assignment or "none" - local name=asset.templatename - local spawned=tostring(asset.spawned) - local groupname=asset.spawngroupname - local typename=asset.unittype - - local mission=self:GetAssetCurrentMission(asset) - local missiontext="" - if mission then - local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 - missiontext=string.format(" [%s (%s): status=%s, distance=%.1f NM]", mission.type, mission.name, mission.status, distance) - end - - -- Mission info. - text=text..string.format("\n -[%d] %s*%d \"%s\": spawned=%s, mission=%s%s", j, typename, asset.nunits, asset.spawngroupname, spawned, tostring(self:IsAssetOnMission(asset)), missiontext) - - -- Payload info. - local payload=asset.payload and table.concat(self:GetPayloadMissionTypes(asset.payload), ", ") or "None" - text=text.." payload="..payload - - -- Flight status. - text=text..", flight: " - if asset.flightgroup and asset.flightgroup:IsAlive() then - local status=asset.flightgroup:GetState() - local fuelmin=asset.flightgroup:GetFuelMin() - local fuellow=asset.flightgroup:IsFuelLow() - local fuelcri=asset.flightgroup:IsFuelCritical() - - text=text..string.format("%s fuel=%d", status, fuelmin) - if fuelcri then - text=text.." (critical!)" - elseif fuellow then - text=text.." (low)" - end - - local lifept, lifept0=asset.flightgroup:GetLifePoints() - text=text..string.format(" life=%d/%d", lifept, lifept0) - - local ammo=asset.flightgroup:GetAmmoTot() - text=text..string.format(" ammo=[G=%d, R=%d, B=%d, M=%d]", ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles) - else - text=text.."N/A" - end - end + if self.verbose>=3 then + local text="Squadrons:" + for i,_squadron in pairs(self.squadrons) do + local squadron=_squadron --Ops.Squadron#SQUADRON + local callsign=squadron.callsignName and UTILS.GetCallsignName(squadron.callsignName) or "N/A" + local modex=squadron.modex and squadron.modex or -1 + local skill=squadron.skill and tostring(squadron.skill) or "N/A" + + -- Squadron text + text=text..string.format("\n* %s %s: %s*%d/%d, Callsign=%s, Modex=%d, Skill=%s", squadron.name, squadron:GetState(), squadron.aircrafttype, squadron:CountAssetsInStock(), #squadron.assets, callsign, modex, skill) end + self:I(self.lid..text) end - self:I(self.lid..text) -------------- -- Mission --- diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 698028882..59755949f 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -98,8 +98,9 @@ -- -- @field #table enrouteTasks Mission enroute tasks. -- --- @field #number missionRepeated Number of times mission was repeated. --- @field #number missionRepeatMax Number of times mission is repeated if failed. +-- @field #number repeated Number of times mission was repeated. +-- @field #number NrepeatFailure Number of times mission is repeated if failed. +-- @field #number NrepeatSuccess Number of times mission is repeated if successful. -- -- @field Ops.OpsGroup#OPSGROUP.Radio radio Radio freq and modulation. -- @field Ops.OpsGroup#OPSGROUP.Beacon tacan TACAN setting. @@ -259,7 +260,7 @@ AUFTRAG = { ClassName = "AUFTRAG", Debug = false, - verbose = 2, + verbose = 0, lid = nil, auftragsnummer = nil, groupdata = {}, @@ -473,19 +474,20 @@ function AUFTRAG:New(Type) -- Auftragsnummer. self.auftragsnummer=_AUFTRAGSNR - -- Log id. + -- Log ID. self:_SetLogID() -- State is planned. self.status=AUFTRAG.Status.PLANNED -- Defaults + self:SetVerbosity(0) self:SetName() self:SetPriority() self:SetTime() self.engageAsGroup=true - self.missionRepeated=0 - self.missionRepeatMax=0 + self.repeated=0 + self.NrepeatFailure=0 self.NrepeatSuccess=0 self.nassets=1 self.dTevaluate=0 @@ -1413,16 +1415,16 @@ function AUFTRAG:SetPriority(Prio, Urgent) return self end ---- Set how many times the mission is repeated if it fails. +--- Set how many times the mission is repeated if it fails. Only valid if the mission is handled by an AIRWING or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. -- @return #AUFTRAG self function AUFTRAG:SetRepeatOnFailure(Nrepeat) - self.missionRepeatMax=Nrepeat or 0 + self.NrepeatFailure=Nrepeat or 0 return self end ---- Set how many times the mission is repeated if it was successful. +--- Set how many times the mission is repeated if it was successful. Only valid if the mission is handled by an AIRWING or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. -- @return #AUFTRAG self @@ -1431,7 +1433,7 @@ function AUFTRAG:SetRepeatOnSuccess(Nrepeat) return self end ---- Define how many assets are required to do the job. +--- Define how many assets are required to do the job. Only valid if the mission is handled by an AIRWING or higher level. -- @param #AUFTRAG self -- @param #number Nassets Number of asset groups. Default 1. -- @return #AUFTRAG self @@ -1459,6 +1461,15 @@ function AUFTRAG:SetEnableMarkers(Coalition) return self end +--- Set verbosity level. +-- @param #AUFTRAG self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #AUFTRAG self +function AUFTRAG:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + --- Set weapon type used for the engagement. -- @param #AUFTRAG self -- @param #number WeaponType Weapon type. Default is `ENUMS.WeaponFlag.Auto`. @@ -1537,11 +1548,11 @@ function AUFTRAG:SetMissionSpeed(Speed) return self end ---- Set max engage range. +--- Set max mission range. Only applies if the AUFTRAG is handled by an AIRWING or CHIEF. This is the max allowed distance from the airbase to the target. -- @param #AUFTRAG self -- @param #number Range Max range in NM. Default 100 NM. -- @return #AUFTRAG self -function AUFTRAG:SetEngageRange(Range) +function AUFTRAG:SetMissionRange(Range) self.engageRange=UTILS.NMToMeters(Range or 100) return self end @@ -2003,6 +2014,7 @@ function AUFTRAG:onafterStatus(From, Event, To) self:E(self.lid..string.format("ERROR: FSM state %s != %s mission status!", fsmstate, self.status)) end + -- General info. if self.verbose>=1 then -- Mission start stop time. @@ -2018,6 +2030,7 @@ function AUFTRAG:onafterStatus(From, Event, To) self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) end + -- Group info. if self.verbose>=2 then -- Data on assigned groups. local text="Group data:" @@ -2508,8 +2521,8 @@ function AUFTRAG:onafterCancel(From, Event, To) self.Tover=timer.getAbsTime() -- No more repeats. - self.missionRepeatMax=self.missionRepeated - self.NrepeatSuccess=self.missionRepeated + self.NrepeatFailure=self.repeated + self.NrepeatSuccess=self.repeated -- Not necessary to delay the evaluaton?! self.dTevaluate=0 @@ -2556,16 +2569,16 @@ function AUFTRAG:onafterSuccess(From, Event, To) self.status=AUFTRAG.Status.SUCCESS self:T(self.lid..string.format("New mission status=%s", self.status)) - if self.missionRepeated>=self.NrepeatSuccess then + if self.repeated>=self.NrepeatSuccess then -- Stop mission. - self:I(self.lid..string.format("Mission SUCCESS! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.missionRepeated, self.NrepeatSuccess)) + self:I(self.lid..string.format("Mission SUCCESS! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.repeated, self.NrepeatSuccess)) self:Stop() else -- Repeat mission. - self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.missionRepeated+1, self.NrepeatSuccess)) + self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, self.NrepeatSuccess)) self:Repeat() end @@ -2582,15 +2595,15 @@ function AUFTRAG:onafterFailed(From, Event, To) self.status=AUFTRAG.Status.FAILED self:T(self.lid..string.format("New mission status=%s", self.status)) - if self.missionRepeated>=self.missionRepeatMax then + if self.repeated>=self.NrepeatFailure then - self:I(self.lid..string.format("Mission FAILED! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.missionRepeated, self.missionRepeatMax)) + self:I(self.lid..string.format("Mission FAILED! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.repeated, self.NrepeatFailure)) self:Stop() else -- Repeat mission. - self:I(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.missionRepeated+1, self.missionRepeatMax)) + self:I(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, self.NrepeatFailure)) self:Repeat() end @@ -2611,17 +2624,22 @@ function AUFTRAG:onafterRepeat(From, Event, To) self:T(self.lid..string.format("New mission status=%s (on Repeat)", self.status)) -- Increase repeat counter. - self.missionRepeated=self.missionRepeated+1 + self.repeated=self.repeated+1 - if self.wingcommander then + if self.chief then + elseif self.wingcommander then + + + elseif self.airwing then -- Already at the airwing ==> Queued() self:Queued(self.airwing) else - + self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, WINGCOMMANDER or AIRWING! Stopping AUFTRAG") + self:Stop() end @@ -2822,8 +2840,8 @@ end -- @return #string Name of the target or "N/A". function AUFTRAG:GetTargetName() - if self.engageTarget.Target then - return self.engageTarget.Name + if self.engageTarget then + return self.engageTarget:GetName() end return "N/A" diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index fdbcae847..2b15fb846 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1503,7 +1503,6 @@ function FLIGHTGROUP:onafterAirborne(From, Event, To) if self.ai then self:_CheckGroupDone(1) else - --if not self.ai then self:_UpdateMenu() end end @@ -1706,7 +1705,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) --- if self:IsAirborne() then - self:T2(self.lid.."No waypoints left ==> CheckGroupDone") + self:I(self.lid.."No waypoints left ==> CheckGroupDone") self:_CheckGroupDone() end @@ -1897,7 +1896,15 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Cancel all missions. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - self:MissionCancel(mission) + local mystatus=mission:GetGroupStatus(self) + + -- Check if mission is already over! + if not (mystatus==AUFTRAG.GroupStatus.DONE or mystatus==AUFTRAG.GroupStatus.CANCELLED) then + local text=string.format("Canceling mission %s in state=%s", mission.name, mission.status) + env.info(text) + self:MissionCancel(mission) + end + end -- Defaults: @@ -1907,7 +1914,6 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Debug message. local text=string.format("Flight group set to hold at airbase %s. SpeedTo=%d, SpeedHold=%d, SpeedLand=%d", airbase:GetName(), SpeedTo, SpeedHold, SpeedLand) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:T(self.lid..text) local althold=self.ishelo and 1000+math.random(10)*100 or math.random(4,10)*1000 @@ -2087,7 +2093,6 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) -- Debug message. local text=string.format("Flight group set to wait/orbit at altitude %d m and speed %.1f km/h", Altitude, Speed) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:T(self.lid..text) --TODO: set ROE passive. introduce roe event/state/variable. @@ -2111,7 +2116,6 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) -- Debug message. local text=string.format("Flight group set to refuel at the nearest tanker") - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) --TODO: set ROE passive. introduce roe event/state/variable. @@ -2146,7 +2150,6 @@ end function FLIGHTGROUP:onafterRefueled(From, Event, To) -- Debug message. local text=string.format("Flight group finished refuelling") - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) -- Check if flight is done. @@ -2169,7 +2172,6 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) self.Tholding=timer.getAbsTime() local text=string.format("Flight group %s is HOLDING now", self.groupname) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:T(self.lid..text) -- Add flight to waiting/holding queue. @@ -2280,7 +2282,6 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) -- Debug message. local text=string.format("Low fuel for flight group %s", self.groupname) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) -- Set switch to true. @@ -2350,7 +2351,6 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) -- Debug message. local text=string.format("Critical fuel for flight group %s", self.groupname) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) -- Set switch to true. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index dbb1896d7..9aeec162b 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -410,11 +410,6 @@ function NAVYGROUP:onafterStatus(From, Event, To) if self.detectionOn then self:_CheckDetectedUnits() end - - -- Current heading and position of the carrier. - local hdg=self:GetHeading() - local pos=self:GetCoordinate() - local speed=self.group:GetVelocityKNOTS() -- Update last known position, orientation, velocity. self:_UpdatePosition() @@ -450,35 +445,40 @@ function NAVYGROUP:onafterStatus(From, Event, To) -- Check if group got stuck. self:_CheckStuck() - - -- Get number of tasks and missions. - local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() - local nMissions=self:CountRemainingMissison() - local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" - local turning=tostring(self:IsTurning()) - local alt=pos.y - local speedExpected=UTILS.MpsToKnots(self.speed or 0) - - local wpidxCurr=self.currentwp - local wpuidCurr=0 - local wpidxNext=self:GetWaypointIndexNext() - local wpuidNext=0 - local wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint()) - local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint(), true) - local roe=self:GetROE() or 0 - local als=self:GetAlarmstate() or 0 + if self.verbose>=1 then - -- Info text. - local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", - fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints, wpDist, wpETA, speed, speedExpected, alt, hdg, turning, freepath, intowind) - self:I(self.lid..text) + -- Get number of tasks and missions. + local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() + local nMissions=self:CountRemainingMissison() + + local intowind=self:IsSteamingIntoWind() and UTILS.SecondsToClock(self.intowind.Tstop-timer.getAbsTime(), true) or "N/A" + local turning=tostring(self:IsTurning()) + local alt=self.position.y + local speed=UTILS.MpsToKnots(self.velocity) + local speedExpected=UTILS.MpsToKnots(self.speed or 0) + + local wpidxCurr=self.currentwp + local wpuidCurr=0 + local wpidxNext=self:GetWaypointIndexNext() + local wpuidNext=0 + local wpDist=UTILS.MetersToNM(self:GetDistanceToWaypoint()) + local wpETA=UTILS.SecondsToClock(self:GetTimeToWaypoint(), true) + local roe=self:GetROE() or 0 + local als=self:GetAlarmstate() or 0 + + -- Info text. + local text=string.format("%s [ROE=%d,AS=%d, T/M=%d/%d]: Wp=%d[%d]-->%d[%d] (of %d) Dist=%.1f NM ETA=%s - Speed=%.1f (%.1f) kts, Depth=%.1f m, Hdg=%03d, Turn=%s Collision=%d IntoWind=%s", + fsmstate, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, #self.waypoints, wpDist, wpETA, speed, speedExpected, alt, self.heading, turning, freepath, intowind) + self:I(self.lid..text) + + end else -- Info text. local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) - self:I(self.lid..text) + self:T(self.lid..text) end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 589891d63..a4cf182ab 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -401,7 +401,17 @@ function OPSGROUP:GetLifePoints() end end ---- Set default cruise speed.. + +--- Set verbosity level. +-- @param #OPSGROUP self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #OPSGROUP self +function OPSGROUP:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Set default cruise speed. -- @param #OPSGROUP self -- @param #number Speed Speed in knots. -- @return #OPSGROUP self @@ -1017,7 +1027,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) self.passedfinalwp=true end - env.info("FF passed final waypoint after remove! current wp = "..self.currentwp) + --env.info("FF passed final waypoint after remove! current wp = "..self.currentwp) self:_CheckGroupDone(1) @@ -1043,8 +1053,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) self.currentwp=self.currentwp-1 end - --self.currentwp=math.max(1, self.currentwp-1) - env.info("FF current waypoint after remove "..self.currentwp) + --env.info("FF current waypoint after remove "..self.currentwp) end @@ -1099,7 +1108,7 @@ function OPSGROUP:SetTask(DCSTask) text=text..string.format("\n[%d] %s", i, tostring(task.id)) end end - self:I(self.lid..text) + self:T(self.lid..text) end return self @@ -1123,7 +1132,7 @@ function OPSGROUP:PushTask(DCSTask) text=text..string.format("\n[%d] %s", i, tostring(task.id)) end end - self:I(self.lid..text) + self:T(self.lid..text) end return self @@ -1367,7 +1376,6 @@ function OPSGROUP:RemoveTask(Task) -- Update route if this is a waypoint task. if task.type==OPSGROUP.TaskType.WAYPOINT and task.status==OPSGROUP.TaskStatus.SCHEDULED then self:_CheckGroupDone(1) - --self:__UpdateRoute(-1) end return true @@ -1445,7 +1453,6 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Debug message. local text=string.format("Task %s ID=%d execute", tostring(Task.description), Task.id) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) -- Cancel current task if there is any. @@ -1556,7 +1563,6 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) -- Debug info. local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)", Task.description, Task.id, Task.stopflag:GetName(), stopflag) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) -- Set stop flag. When the flag is true, the _TaskDone function is executed and calls :TaskDone() @@ -1617,7 +1623,6 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) -- Debug message. local text=string.format("Task done: %s ID=%d", Task.description, Task.id) - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) -- No current task. @@ -1979,7 +1984,11 @@ end function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) if self.currentmission and Mission.auftragsnummer==self.currentmission then - + + --- + -- Current Mission + --- + -- Get mission waypoint task. local Task=Mission:GetGroupWaypointTask(self) @@ -1995,11 +2004,15 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) else - -- Not the current mission. - -- TODO: remove mission from queue? + --- + -- NOT the current mission + --- -- Set mission group status. - Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.CANCELLED) + Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.CANCELLED) + + -- Remove mission from queue + self:RemoveMission(Mission) -- Send group RTB or WAIT if nothing left to do. self:_CheckGroupDone(1) @@ -2019,7 +2032,6 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Debug info. local text=string.format("Mission %s DONE!", Mission.name) self:I(self.lid..text) - MESSAGE:New(text, 30, self.groupname):ToAllIf(self.Debug) -- Set group status. Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.DONE) @@ -2241,7 +2253,6 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", tostring(wpindex), #self.waypoints, Waypoint.uid, tostring(self.passedfinalwp), tostring(Waypoint.detour), tostring(Waypoint.astar)) self:I(self.lid..text) - MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) end @@ -2623,12 +2634,12 @@ end -- @param #OPSGROUP self function OPSGROUP:_PrintTaskAndMissionStatus() - --- - -- Tasks: verbose >= 2 + --- + -- Tasks: verbose >= 3 --- -- Task queue. - if #self.taskqueue>0 and self.verbose>=2 then + if self.verbose>=3 and #self.taskqueue>0 then local text=string.format("Tasks #%d", #self.taskqueue) for i,_task in pairs(self.taskqueue) do local task=_task --Ops.OpsGroup#OPSGROUP.Task @@ -2660,11 +2671,11 @@ function OPSGROUP:_PrintTaskAndMissionStatus() end --- - -- Missions: verbose>=1 + -- Missions: verbose>=2 --- -- Current mission name. - if self.verbose>0 then + if self.verbose>=2 then local Mission=self:GetMissionByID(self.currentmission) -- Current status. @@ -2687,7 +2698,8 @@ end --- Enhance waypoint table. -- @param #OPSGROUP self --- @return #OPSGROUP.Waypoint Waypoint data. +-- @param #OPSGROUP.Waypoint Waypoint data. +-- @return #OPSGROUP.Waypoint Modified waypoint data. function OPSGROUP:_CreateWaypoint(waypoint) -- Set uid. diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 8fe221598..b21ca511b 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -136,15 +136,20 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) -- Defaults. self.Ngroups=Ngroups or 3 - self:SetEngagementRange() + self:SetMissionRange() + self:SetSkill(AI.Skill.GOOD) + self:SetVerbosity(0) -- Everyone can ORBIT. self:AddMissionCapability(AUFTRAG.Type.ORBIT) + -- Generalized attribute. self.attribute=self.templategroup:GetAttribute() + -- Aircraft type. self.aircrafttype=self.templategroup:GetTypeName() + -- Refueling system. self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable()) self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker()) @@ -201,8 +206,6 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - self.Debug=true - return self end @@ -241,10 +244,19 @@ function SQUADRON:SetSkill(Skill) return self end +--- Set verbosity level. +-- @param #SQUADRON self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #SQUADRON self +function SQUADRON:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + --- Set turnover and repair time. If an asset returns from a mission to the airwing, it will need some time until the asset is available for further missions. -- @param #SQUADRON self -- @param #number MaintenanceTime Time in minutes it takes until a flight is combat ready again. Default is 0 min. --- @param #number RepairTime Time in minutes it takes to repair a flight for each percent damage taken. Default is 0 min. +-- @param #number RepairTime Time in minutes it takes to repair a flight for each life point taken. Default is 0 min. -- @return #SQUADRON self function SQUADRON:SetTurnoverTime(MaintenanceTime, RepairTime) self.maintenancetime=MaintenanceTime and MaintenanceTime*60 or 0 @@ -350,12 +362,12 @@ function SQUADRON:GetMissionPeformance(MissionType) return -1 end ---- Set max engagement range. +--- Set max mission range. Only missions in a circle of this radius around the squadron airbase are executed. -- @param #SQUADRON self --- @param #number EngageRange Engagement range in NM. Default 80 NM. +-- @param #number Range Range in NM. Default 100 NM. -- @return #SQUADRON self -function SQUADRON:SetEngagementRange(EngageRange) - self.engageRange=UTILS.NMToMeters(EngageRange or 80) +function SQUADRON:SetMissionRange(Range) + self.engageRange=UTILS.NMToMeters(Range or 100) return self end @@ -565,27 +577,31 @@ end -- @param #string To To state. function SQUADRON:onafterStatus(From, Event, To) - -- FSM state. - local fsmstate=self:GetState() + if self.verbose>=1 then - local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A" - local modex=self.modex and self.modex or -1 - local skill=self.skill and tostring(self.skill) or "N/A" + -- FSM state. + local fsmstate=self:GetState() - local NassetsTot=#self.assets - local NassetsInS=self:CountAssetsInStock() - local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 - if self.airwing then - NassetsQP, NassetsP, NassetsQ=self.airwing:CountAssetsOnMission(nil, self) - end - - -- Short info. - local text=string.format("%s [Type=%s, Callsign=%s, Modex=%d, Skill=%s]: Assets Total=%d, InStock=%d, OnMission=%d [P=%d, Q=%d]", - fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ) - self:I(self.lid..text) - - -- Check if group has detected any units. - self:_CheckAssetStatus() + local callsign=self.callsignName and UTILS.GetCallsignName(self.callsignName) or "N/A" + local modex=self.modex and self.modex or -1 + local skill=self.skill and tostring(self.skill) or "N/A" + + local NassetsTot=#self.assets + local NassetsInS=self:CountAssetsInStock() + local NassetsQP=0 ; local NassetsP=0 ; local NassetsQ=0 + if self.airwing then + NassetsQP, NassetsP, NassetsQ=self.airwing:CountAssetsOnMission(nil, self) + end + + -- Short info. + local text=string.format("%s [Type=%s, Call=%s, Modex=%d, Skill=%s]: Assets Total=%d, Stock=%d, Mission=%d [Active=%d, Queue=%d]", + fsmstate, self.aircrafttype, callsign, modex, skill, NassetsTot, NassetsInS, NassetsQP, NassetsP, NassetsQ) + self:I(self.lid..text) + + -- Check if group has detected any units. + self:_CheckAssetStatus() + + end if not self:IsStopped() then self:__Status(-30) @@ -597,7 +613,8 @@ end -- @param #SQUADRON self function SQUADRON:_CheckAssetStatus() - if self.verbose>=0 then + if self.verbose>=2 then + local text="" for j,_asset in pairs(self.assets) do local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset @@ -647,8 +664,7 @@ function SQUADRON:_CheckAssetStatus() -- Payload info. local payload=asset.payload and table.concat(self.airwing:GetPayloadMissionTypes(asset.payload), ", ") or "None" text=text..", Payload={"..payload.."}" - - + else --- diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index e47770ef4..9f37e0b7f 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -136,9 +136,12 @@ function TARGET:New(TargetObject) self:AddObject(TargetObject) local Target=self.targets[1] --#TARGET.Object + + self.name=self:GetTargetName(Target) self.category=self:GetTargetCategory(Target) + -- Log ID. self.lid=string.format("TARGET #%03d %s | ", _TARGETID, self.category) -- Start state. @@ -660,6 +663,56 @@ function TARGET:GetTargetCoordinate(Target) return nil end +--- Get target name. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return #string Name of the target object. +function TARGET:GetTargetName(Target) + + if Target.Type==TARGET.ObjectType.GROUP then + + if Target.Object and Target.Object:IsAlive() then + + return Target.Object:GetName() + + end + + elseif Target.Type==TARGET.ObjectType.UNIT then + + if Target.Object and Target.Object:IsAlive() then + return Target.Object:GetName() + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + if Target.Object and Target.Object:IsAlive() then + return Target.Object:GetName() + end + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + if Target.Status==TARGET.ObjectStatus.ALIVE then + return Target.Object:GetName() + end + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + local coord=Target.Object --Core.Point#COORDINATE + + return coord:ToStringMGRS() + + end + + return "Unknown" +end + +--- Get name. +-- @param #TARGET self +-- @return #string Name of the target usually the first object. +function TARGET:GetName() + return self.name +end + --- Get coordinate. -- @param #TARGET self -- @return Core.Point#COORDINATE Coordinate of the target. diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 1ab6368ff..a5b2d9bbf 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -1,6 +1,6 @@ --- **Utils** - Lua Profiler. -- --- +-- Find out how many times functions are called and how much real time it costs. -- -- === -- @@ -13,11 +13,17 @@ --- PROFILER class. -- @type PROFILER -- @field #string ClassName Name of the class. --- @field #table Counters Counters. +-- @field #table Counters Function counters. -- @field #table dInfo Info. -- @field #table fTime Function time. -- @field #table fTimeTotal Total function time. -- @field #table eventhandler Event handler to get mission end event. +-- @field #number TstartGame Game start time timer.getTime(). +-- @field #number TstartOS OS real start time os.clock. +-- @field #boolean logUnknown Log unknown functions. Default is off. +-- @field #number lowCpsThres Low calls per second threashold. Only write output if function has more calls per second than this value. +-- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler". +-- @field #string fileNameSuffix Output file name prefix, e.g. "txt" --- *The emperor counsels simplicity. First principles. Of each particular thing, ask: What is it in itself, in its own constitution? What is its causal nature? * -- @@ -27,8 +33,8 @@ -- -- # The PROFILER Concept -- --- Profile your lua code. This tells you, which functions are called very often and which consume most CPU time. --- With this information you could optimize the perfomance of your code. +-- Profile your lua code. This tells you, which functions are called very often and which consume most real time. +-- With this information you can optimize the perfomance of your code. -- -- # Prerequisites -- @@ -37,19 +43,22 @@ -- -- # Start -- --- The profiler can simply be started by +-- The profiler can simply be started with the @{#PROFILER.Start}(*Delay, Duration*) function -- -- PROFILER.Start() -- --- The start can be delayed by specifying a the amount of seconds as argument, e.g. PROFILER.Start(60) to start profiling in 60 seconds. +-- The optional parameter *Delay* can be used to delay the start by a certain amount of seconds and the optional parameter *Duration* can be used to +-- stop the profiler after a certain amount of seconds. -- -- # Stop -- --- The profiler automatically stops when the mission ends. But it can be stopped any time by calling +-- The profiler automatically stops when the mission ends. But it can be stopped any time with the @{#PROFILER.Stop}(*Delay*) function -- -- PROFILER.Stop() -- --- The stop call can be delayed by specifying the delay in seconds as optional argument, e.g. PROFILER.Stop(120) to stop it in 120 seconds. +-- The optional parameter *Delay* can be used to specify a delay after which the profiler is stopped. +-- +-- When the profiler is stopped, the output is written to a file. -- -- # Output -- @@ -57,13 +66,19 @@ -- -- X:\User\\Saved Games\DCS OpenBeta\Logs -- --- ## Sort Output +-- The default file name is "MooseProfiler.txt". If that file exists, the file name is "MooseProfiler-001.txt" etc. -- --- By default the output is sorted with respect to the total time a function used. +-- ## Data -- --- The output can also be sorted with respect to the number of times the function was called by setting +-- The data in the output file provides information on the functions that were called in the mission. -- --- PROFILER.sortBy=1 +-- It will tell you how many times a function was called in total, how many times per second, how much time in total and the percentage of time. +-- +-- If you only want output for functions that are called more than X times per second, you can set +-- +-- PROFILER.lowCpsThres=1.5 +-- +-- With this setting, only functions which are called more than 1.5 times per second are displayed. -- -- @field #PROFILER PROFILER = { @@ -73,11 +88,10 @@ PROFILER = { fTime = {}, fTimeTotal = {}, eventHandler = {}, - startTime = nil, - runTime = nil, logUnknown = false, - lowCpsThres = 5, - fileName = "", + lowCpsThres = 0.0, + fileNamePrefix = "MooseProfiler", + fileNameSuffix = "txt" } --- Waypoint data. @@ -88,10 +102,6 @@ PROFILER = { -- @field #number count Number of function calls. -- @field #number tm Total time in seconds. -PROFILER.logUnknown=false -- Log unknown functions -PROFILER.lowCpsThres=0 -- Skip results with less than X calls per second -PROFILER.fileName="_LuaProfiler.txt" - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start/Stop Profiler ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -104,28 +114,58 @@ function PROFILER.Start(Delay, Duration) if Delay and Delay>0 then BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) else + + -- Check if os and lfs are available. + local go=true + if not os then + error("Profiler needs os to be desanitized") + go=false + end + if not lfs then + error("Profiler needs lfs to be desanitized") + go=false + end + if not go then + return + end + -- Set start time. PROFILER.TstartGame=timer.getTime() PROFILER.TstartOS=os.clock() -- Add event handler. world.addEventHandler(PROFILER.eventHandler) + --[[ -- Message to screen. local function showProfilerRunning() timer.scheduleFunction(showProfilerRunning, nil, timer.getTime()+600) trigger.action.outText("### Profiler running ###", 600) - end - + end -- Message. showProfilerRunning() + ]] -- Info in log. - BASE:I('############################ Profiler Started ############################') + env.info('############################ Profiler Started ############################') + if Duration then + env.info(string.format("Duration %d seconds", Duration)) + else + env.info(string.format("Stopped when mission ends")) + end + env.info(string.format("Calls per second threshold %.3f/sec", PROFILER.lowCpsThres)) + env.info(string.format("Log file \"%s.%s\"", PROFILER.fileNamePrefix, PROFILER.fileNameSuffix)) + env.info('###############################################################################') + + + -- Message on screen + local duration=Duration or 600 + trigger.action.outText("### Profiler running ###", duration) -- Set hook. debug.sethook(PROFILER.hook, "cr") + -- Auto stop profiler. if Duration then PROFILER.Stop(Duration) end @@ -148,12 +188,14 @@ function PROFILER.Stop(Delay) debug.sethook() - -- Run time. - PROFILER.runTimeGame=timer.getTime()-PROFILER.TstartGame - PROFILER.runTimeOS=os.clock()-PROFILER.TstartOS + -- Run time game. + local runTimeGame=timer.getTime()-PROFILER.TstartGame + + -- Run time real OS. + local runTimeOS=os.clock()-PROFILER.TstartOS -- Show info. - PROFILER.showInfo() + PROFILER.showInfo(runTimeGame, runTimeOS) end @@ -237,20 +279,20 @@ end --- Show table. -- @param #table data Data table. -- @param #function f The file. --- @param #boolean detailed Show detailed info. -function PROFILER.showTable(data, f, detailed) +-- @param #number runTimeGame Game run time in seconds. +function PROFILER.showTable(data, f, runTimeGame) -- Loop over data. for i=1, #data do local t=data[i] --#PROFILER.Data -- Calls per second. - local cps=t.count/PROFILER.runTimeGame + local cps=t.count/runTimeGame if (cps>=PROFILER.lowCpsThres) then -- Output - local text=string.format("%30s: %8d calls %8.1f/sec - Time %8.3f sec (%.3f %%) %s line %s", t.func, t.count, cps, t.tm, t.tm/PROFILER.runTimeGame*100, tostring(t.src), tostring(t.line)) + local text=string.format("%30s: %8d calls %8.1f/sec - Time %8.3f sec (%.3f %%) %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, tostring(t.src), tostring(t.line)) PROFILER._flog(f, text) end @@ -259,16 +301,50 @@ function PROFILER.showTable(data, f, detailed) end --- Write info to output file. -function PROFILER.showInfo() +-- @return #string File name. +function PROFILER.getfilename() + + local dir=lfs.writedir()..[[Logs\]] + + local file=dir..PROFILER.fileNamePrefix.."."..PROFILER.fileNameSuffix + + if not UTILS.FileExists(file) then + return file + end + + for i=1,999 do + + local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, PROFILER.fileNameSuffix) + + if not UTILS.FileExists(file) then + return file + end + + end + +end + +--- Write info to output file. +-- @param #number runTimeGame Game time in seconds. +-- @param #number runTimeOS OS time in seconds. +function PROFILER.showInfo(runTimeGame, runTimeOS) -- Output file. - local file=lfs.writedir()..[[Logs\]]..PROFILER.fileName + local file=PROFILER.getfilename() local f=io.open(file, 'w') -- Gather data. local Ttot=0 local Calls=0 + local t={} + + local tcopy=nil --#PROFILER.Data + local tserialize=nil --#PROFILER.Data + local tforgen=nil --#PROFILER.Data + local tpairs=nil --#PROFILER.Data + + for func, count in pairs(PROFILER.Counters) do local s,src,line,tm=PROFILER.getData(func) @@ -277,26 +353,80 @@ function PROFILER.showInfo() if s==nil then s="" end end - if s~=nil and s~="_copy" and s~="_Serialize" and s~="(for generator)" and s~="pairs" then - t[#t+1]= + if s~=nil then + + -- Profile data. + local T= { func=s, src=src, line=line, count=count, tm=tm, - } + } --#PROFILER.Data + + -- Collect special cases. Somehow, e.g. "_copy" appears multiple times so we try to gather all data. + if s=="_copy" then + if tcopy==nil then + tcopy=T + else + tcopy.count=tcopy.count+T.count + tcopy.tm=tcopy.tm+T.tm + end + elseif s=="_Serialize" then + if tserialize==nil then + tserialize=T + else + tserialize.count=tserialize.count+T.count + tserialize.tm=tserialize.tm+T.tm + end + elseif s=="(for generator)" then + if tforgen==nil then + tforgen=T + else + tforgen.count=tforgen.count+T.count + tforgen.tm=tforgen.tm+T.tm + end + elseif s=="pairs" then + if tpairs==nil then + tpairs=T + else + tpairs.count=tpairs.count+T.count + tpairs.tm=tpairs.tm+T.tm + end + else + table.insert(t, T) + end + + -- Total function time. Ttot=Ttot+tm + + -- Total number of calls. Calls=Calls+count + end - + end + + -- Add special cases. + if tcopy then + table.insert(t, tcopy) + end + if tserialize then + table.insert(t, tserialize) + end + if tforgen then + table.insert(t, tforgen) + end + if tpairs then + table.insert(t, tpairs) + end env.info("**************************************************************************************************") env.info(string.format("Profiler")) env.info(string.format("--------")) - env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(PROFILER.runTimeGame, true), PROFILER.runTimeGame)) - env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(PROFILER.runTimeOS, true), PROFILER.runTimeOS)) - env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/PROFILER.runTimeGame*100)) + env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame)) + env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS)) + env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100)) env.info(string.format("* Total functions : %d", #t)) env.info(string.format("* Total func calls : %d", Calls)) env.info(string.format("* Writing to file : \"%s\"", file)) @@ -315,16 +445,16 @@ function PROFILER.showInfo() PROFILER._flog(f,"---- Profiler Report ----") PROFILER._flog(f,"-------------------------") PROFILER._flog(f,"") - PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(PROFILER.runTimeGame, true), PROFILER.runTimeGame)) - PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(PROFILER.runTimeOS, true), PROFILER.runTimeOS).." (can vary significantly compared to the game time)") - PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/PROFILER.runTimeGame*100)) + PROFILER._flog(f,string.format("* Runtime Game : %s = %.1f sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame)) + PROFILER._flog(f,string.format("* Runtime Real : %s = %.1f sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS)) + PROFILER._flog(f,string.format("* Function time : %s = %.1f sec (%.1f %% of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100)) PROFILER._flog(f,"") PROFILER._flog(f,string.format("* Total functions = %d", #t)) PROFILER._flog(f,string.format("* Total func calls = %d", Calls)) PROFILER._flog(f,"") PROFILER._flog(f,"************************************************************************************************************************") PROFILER._flog(f,"") - PROFILER.showTable(t, f, true) + PROFILER.showTable(t, f, runTimeGame) -- Sort by number of calls. table.sort(t, function(a,b) return a.count>b.count end) @@ -337,7 +467,7 @@ function PROFILER.showInfo() PROFILER._flog(f,"---- Data Sorted by Calls ----") PROFILER._flog(f,"------------------------------") PROFILER._flog(f,"") - PROFILER.showTable(t, f, true) + PROFILER.showTable(t, f, runTimeGame) -- Closing. PROFILER._flog(f,"") diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 3cc74baec..5d65d3fbe 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -755,7 +755,7 @@ end -- @param #string Text The updated text, displayed in the mark panel. function MARKER:onafterTextUpdate(From, Event, To, Text) - self:I(self.lid..string.format("New Marker Text:\n%s", Text)) + self:T(self.lid..string.format("New Marker Text:\n%s", Text)) end @@ -767,7 +767,7 @@ end -- @param Core.Point#COORDINATE Coordinate The updated coordinates. function MARKER:onafterCoordUpdate(From, Event, To, Coordinate) - self:I(self.lid..string.format("New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS())) + self:T(self.lid..string.format("New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS())) end From e9e6a63e6a4090a169dd71d0139c580b60dd0cad Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 18 Aug 2020 01:35:24 +0200 Subject: [PATCH 40/79] Ops --- .../Moose/Functional/Warehouse.lua | 14 ++-- Moose Development/Moose/Ops/AirWing.lua | 80 +++++++++++++------ Moose Development/Moose/Ops/ArmyGroup.lua | 1 + Moose Development/Moose/Ops/Auftrag.lua | 68 +++++++++++----- Moose Development/Moose/Ops/FlightGroup.lua | 4 +- Moose Development/Moose/Ops/OpsGroup.lua | 12 +-- Moose Development/Moose/Ops/Squadron.lua | 5 +- Moose Development/Moose/Ops/Target.lua | 1 + .../Moose/Utilities/Profiler.lua | 36 ++++----- 9 files changed, 143 insertions(+), 78 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 6f228c997..82d09a386 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -46,7 +46,7 @@ -- @type WAREHOUSE -- @field #string ClassName Name of the class. -- @field #boolean Debug If true, send debug messages to all. --- @field #number verbose Verbosity level. +-- @field #number verbosity Verbosity level. -- @field #string wid Identifier of the warehouse printed before other output to DCS.log file. -- @field #boolean Report If true, send status messages to coalition. -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. @@ -1890,7 +1890,7 @@ function WAREHOUSE:New(warehouse, alias) -- Defaults self:SetMarker(true) self:SetReportOff() - self:SetVerbosity(0) + self:SetVerbosityLevel(0) -- Add warehouse to database. _WAREHOUSEDB.Warehouses[self.uid]=self @@ -2556,8 +2556,8 @@ end -- @param #WAREHOUSE self -- @param #number VerbosityLevel Level of output (higher=more). Default 0. -- @return #WAREHOUSE self -function WAREHOUSE:SetVerbosity(VerbosityLevel) - self.verbose=VerbosityLevel or 0 +function WAREHOUSE:SetVerbosityLevel(VerbosityLevel) + self.verbosity=VerbosityLevel or 0 return self end @@ -3403,7 +3403,7 @@ end function WAREHOUSE:onafterStatus(From, Event, To) -- General info. - if self.verbose>=1 then + if self.verbosity>=1 then local FSMstate=self:GetState() @@ -8289,7 +8289,7 @@ end -- @param #string name Name of the queue for info reasons. function WAREHOUSE:_PrintQueue(queue, name) - if self.verbose>=2 then + if self.verbosity>=2 then local total="Empty" if #queue>0 then @@ -8360,7 +8360,7 @@ end --- Display status of warehouse. -- @param #WAREHOUSE self function WAREHOUSE:_DisplayStatus() - if self.verbose>=3 then + if self.verbosity>=3 then local text=string.format("\n------------------------------------------------------\n") text=text..string.format("Warehouse %s status: %s\n", self.alias, self:GetState()) text=text..string.format("------------------------------------------------------\n") diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index e2633f33c..fa3e963a5 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -154,7 +154,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.2.1" +AIRWING.version="0.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -204,7 +204,7 @@ function AIRWING:New(warehousename, airwingname) self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. -- Defaults: - self:SetVerbosity(0) + self:SetVerbosity(2) self.nflightsCAP=0 self.nflightsAWACS=0 self.nflightsTANKERboom=0 @@ -341,8 +341,8 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) end -- Info - self:I(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: N=%d (unlimited=%s), performance=%d, missions: %s", - payload.unitname, payload.aircrafttype, payload.navail, tostring(payload.unlimited), Performance, table.concat(MissionTypes, ", "))) + self:I(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", + payload.unitname, payload.aircrafttype, payload.uid, payload.navail, tostring(payload.unlimited), Performance, table.concat(MissionTypes, ", "))) -- Add payload table.insert(self.payloads, payload) @@ -438,13 +438,13 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) local function _checkPayloads(payload) if Payloads then for _,Payload in pairs(Payloads) do - if Payload.uid==payload.id then + if Payload.uid==payload.uid then return true end end else -- Payload was not specified. - return true + return nil end return false end @@ -453,7 +453,13 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) local payloads={} for _,_payload in pairs(self.payloads) do local payload=_payload --#AIRWING.Payload - if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType, payload.capabilities) and payload.navail>0 and _checkPayloads(payload) then + + local specialpayload=_checkPayloads(payload) + local compatible=self:CheckMissionCapability(MissionType, payload.capabilities) + + local goforit = specialpayload or (specialpayload==nil and compatible) + + if payload.aircrafttype==UnitType and payload.navail>0 and goforit then table.insert(payloads, payload) end end @@ -807,15 +813,18 @@ function AIRWING:onafterStatus(From, Event, To) if self.verbose>=1 then -- Count missions not over yet. - local nmissions=self:CountMissionsInQueue() + local Nmissions=self:CountMissionsInQueue() -- Count ALL payloads in stock. If any payload is unlimited, this gives 999. local Npayloads=self:CountPayloadsInStock(AUFTRAG.Type) - - -- TODO: assets total - + + -- Assets tot + local Npq, Np, Nq=self:CountAssetsOnMission() + + local assets=string.format("%d [Mission=%d (Active=%d, Queued=%d)]", self:CountAssets(), Npq, Np, Nq) + -- Output. - local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d", fsmstate, nmissions, Npayloads, #self.payloads, #self.squadrons) + local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.squadrons, assets) self:I(self.lid..text) end @@ -826,7 +835,12 @@ function AIRWING:onafterStatus(From, Event, To) local text=string.format("Missions Total=%d:", #self.missionqueue) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - text=text..string.format("\n[%d] %s: Status=%s, Nassets=%d, Prio=%d, ID=%d (%s)", i, mission.type, mission.status, mission.nassets, mission.prio, mission.auftragsnummer, mission.name) + + local prio=string.format("%d/%d", mission.prio, mission.importance) ; if mission.urgent then prio=prio.." (!)" end + local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) + local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) + + text=text..string.format("\n[%d] %s %s: Status=%s, Prio=%s, Assets=%s, Targets=%s", i, mission.name, mission.type, mission.status, prio, assets, target) end self:I(self.lid..text) end @@ -1110,13 +1124,13 @@ function AIRWING:_GetNextMission() table.sort(self.missionqueue, _sort) -- Look for first mission that is SCHEDULED. - local importance=math.huge + local vip=math.huge for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission.importance (QUEUED) --> (REQUESTED) --> SCHEDULED --> STARTED --> EXECUTING --> DONE + self:AddTransition("*", "Planned", AUFTRAG.Status.PLANNED) -- Mission is in planning stage. self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. @@ -1406,12 +1406,12 @@ end -- @param #AUFTRAG self -- @param #number Prio Priority 1=high, 100=low. Default 50. -- @param #boolean Urgent If *true*, another running mission might be cancelled if it has a lower priority. --- @param #number Importance Number 1-10. If missions with lower value are in the queue, these have to be finished first. +-- @param #number Importance Number 1-10. If missions with lower value are in the queue, these have to be finished first. Default is 5. -- @return #AUFTRAG self -function AUFTRAG:SetPriority(Prio, Urgent) +function AUFTRAG:SetPriority(Prio, Urgent, Importance) self.prio=Prio or 50 self.urgent=Urgent - self.importance=5 + self.importance=Importance or 5 return self end @@ -1730,15 +1730,15 @@ function AUFTRAG:AssignSquadrons(Squadrons) self.squadrons=Squadrons end ---- Set the required payload for this mission. Only available for use with an AIRWING. +--- Add a required payload for this mission. Only these payloads will be used for this mission. If they are not available, the mission cannot start. Only available for use with an AIRWING. -- @param #AUFTRAG self --- @param Ops.AirWing#AIRWING.Payload Required payload +-- @param Ops.AirWing#AIRWING.Payload Payload Required payload. -- @return #AUFTRAG self function AUFTRAG:AddRequiredPayload(Payload) self.payloads=self.payloads or {} - table.insert(self.payload, Payload) + table.insert(self.payloads, Payload) end @@ -1985,6 +1985,7 @@ function AUFTRAG:onafterStatus(From, Event, To) -- Number of alive mission targets. local Ntargets=self:CountMissionTargets() + local Ntargets0=self:GetTargetInitialNumber() -- Number of alive groups attached to this mission. local Ngroups=self:CountOpsGroups() @@ -1997,7 +1998,7 @@ function AUFTRAG:onafterStatus(From, Event, To) -- All groups have reported MISSON DONE. self:Done() - elseif (self.Tstop and Tnow>self.Tstop+10) or (self.Ntargets>0 and Ntargets==0) then + elseif (self.Tstop and Tnow>self.Tstop+10) or (Ntargets0>0 and Ntargets==0) then -- Cancel mission if stop time passed. self:Cancel() @@ -2075,8 +2076,10 @@ function AUFTRAG:Evaluate() -- Current number of mission targets. local Ntargets=self:CountMissionTargets() + local Ntargets0=self:GetTargetInitialNumber() - if self.Ntargets>0 then + + if Ntargets0>0 then --- -- Mission had targets @@ -2085,7 +2088,7 @@ function AUFTRAG:Evaluate() -- Number of current targets is still >0 ==> Not everything was destroyed. if self.type==AUFTRAG.Type.TROOPTRANSPORT then - if Ntargets Queued() - self:Queued(self.airwing) + self:Queued(self.airwing) else self:E(self.lid.."ERROR: Mission can only be repeated by a CHIEF, WINGCOMMANDER or AIRWING! Stopping AUFTRAG") @@ -2721,11 +2740,8 @@ function AUFTRAG:_TargetFromObject(Object) end - -- TODO: get rid of this. - self.Ntargets=self.engageTarget.Ntargets0 - -- Debug info. - self:T(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", self.engageTarget.lid, self.engageTarget.lid, self.Ntargets, self.engageTarget:GetLife())) + self:T(self.lid..string.format("Mission Target %s Type=%s, Ntargets=%d, Lifepoints=%d", self.engageTarget.lid, self.engageTarget.lid, self.engageTarget.Ntargets0, self.engageTarget:GetLife())) return self end @@ -2733,9 +2749,8 @@ end --- Count alive mission targets. -- @param #AUFTRAG self --- @param #AUFTRAG.TargetData Target (Optional) The target object. -- @return #number Number of alive target units. -function AUFTRAG:CountMissionTargets(Target) +function AUFTRAG:CountMissionTargets() if self.engageTarget then return self.engageTarget:CountTargets() @@ -2745,6 +2760,19 @@ function AUFTRAG:CountMissionTargets(Target) end +--- Get initial number of targets. +-- @param #AUFTRAG self +-- @return #number Number of initial life points when mission was planned. +function AUFTRAG:GetTargetInitialNumber() + local target=self:GetTargetData() + if target then + return target.Ntargets0 + else + return 0 + end +end + + --- Get target life points. -- @param #AUFTRAG self -- @return #number Number of initial life points when mission was planned. @@ -2999,7 +3027,7 @@ function AUFTRAG:UpdateMarker() -- Marker text. local text=string.format("%s %s: %s", self.name, self.type:upper(), self.status:upper()) text=text..string.format("\n%s", self:GetTargetName()) - text=text..string.format("\nTargets %d/%d, Life Points=%d/%d", self:CountMissionTargets(), self.Ntargets, self:GetTargetLife(), self:GetTargetInitialLife()) + text=text..string.format("\nTargets %d/%d, Life Points=%d/%d", self:CountMissionTargets(), self:GetTargetInitialNumber(), self:GetTargetLife(), self:GetTargetInitialLife()) text=text..string.format("\nFlights %d/%d", self:CountOpsGroups(), self.nassets) if not self.marker then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2b15fb846..5ac377611 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -248,7 +248,7 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. - self:AddTransition("*", "Wait", "Waiting") -- Group is orbiting. + self:AddTransition("*", "Wait", "*") -- Group is orbiting. self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. @@ -2119,9 +2119,9 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) self:I(self.lid..text) --TODO: set ROE passive. introduce roe event/state/variable. - --TODO: cancel current task + -- Pause current mission if there is any. self:PauseMission() -- Refueling task. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index a4cf182ab..d6fb149fa 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1766,11 +1766,11 @@ function OPSGROUP:_GetNextMission() local time=timer.getAbsTime() -- Look for first mission that is SCHEDULED. - local importance=math.huge + local vip=math.huge for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission.importanceengagerange then - self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max engage Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) + self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max mission Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange))) return false end @@ -833,7 +833,8 @@ function SQUADRON:RecruitAssets(Mission, Npayloads) if Mission.type==AUFTRAG.Type.INTERCEPT then combatready=flightgroup:CanAirToAir() else - combatready=flightgroup:CanAirToGround() + local excludeguns=Mission.type==AUFTRAG.Type.BOMBING or Mission.type==AUFTRAG.Type.BOMBRUNWAY or Mission.type==AUFTRAG.Type.BOMBCARPET + combatready=flightgroup:CanAirToGround(excludeguns) end -- No more attacks if fuel is already low. Safety first! diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 9f37e0b7f..e5b2f3a22 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -104,6 +104,7 @@ TARGET.ObjectStatus={ -- @field #number Life0 Life points of completely healthy target. -- @field #string Status Status "Alive" or "Dead". +--- Global target ID counter. _TARGETID=0 --- TARGET class version. diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index a5b2d9bbf..639e7174a 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -111,24 +111,24 @@ PROFILER = { -- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends. function PROFILER.Start(Delay, Duration) + -- Check if os and lfs are available. + local go=true + if not os then + error("Profiler needs os to be desanitized") + go=false + end + if not lfs then + error("Profiler needs lfs to be desanitized") + go=false + end + if not go then + return + end + if Delay and Delay>0 then BASE:ScheduleOnce(Delay, PROFILER.Start, 0, Duration) else - -- Check if os and lfs are available. - local go=true - if not os then - error("Profiler needs os to be desanitized") - go=false - end - if not lfs then - error("Profiler needs lfs to be desanitized") - go=false - end - if not go then - return - end - -- Set start time. PROFILER.TstartGame=timer.getTime() PROFILER.TstartOS=os.clock() @@ -149,12 +149,12 @@ function PROFILER.Start(Delay, Duration) -- Info in log. env.info('############################ Profiler Started ############################') if Duration then - env.info(string.format("Duration %d seconds", Duration)) + env.info(string.format("- Will be running for %d seconds", Duration)) else - env.info(string.format("Stopped when mission ends")) + env.info(string.format("- Will be stopped when mission ends")) end - env.info(string.format("Calls per second threshold %.3f/sec", PROFILER.lowCpsThres)) - env.info(string.format("Log file \"%s.%s\"", PROFILER.fileNamePrefix, PROFILER.fileNameSuffix)) + env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.lowCpsThres)) + env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename())) env.info('###############################################################################') From 114032a743ee6143a69fb86d3cf09b75af6d8df8 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 18 Aug 2020 18:15:22 +0200 Subject: [PATCH 41/79] Ops --- Moose Development/Moose/Core/Set.lua | 13 +-- Moose Development/Moose/Ops/AirWing.lua | 59 ++++++----- Moose Development/Moose/Ops/ArmyGroup.lua | 2 +- Moose Development/Moose/Ops/Auftrag.lua | 45 +++++++-- Moose Development/Moose/Ops/FlightGroup.lua | 2 + Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Ops/Squadron.lua | 47 ++++++--- Moose Development/Moose/Ops/Target.lua | 106 ++++++++++++++------ Moose Development/Moose/Wrapper/Group.lua | 2 + 9 files changed, 191 insertions(+), 87 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index a677f1135..13f0f6f31 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -3238,13 +3238,13 @@ do -- SET_STATIC self:F3( { Event } ) if Event.IniObjectCategory == Object.Category.STATIC then - if not self.Database[Event.IniDCSStaticName] then - self.Database[Event.IniDCSStaticName] = STATIC:Register( Event.IniDCSStaticName ) - self:T3( self.Database[Event.IniDCSStaticName] ) + if not self.Database[Event.IniDCSUnitName] then + self.Database[Event.IniDCSUnitName] = STATIC:Register( Event.IniDCSUnitName ) + self:T3( self.Database[Event.IniDCSUnitName] ) end end - return Event.IniDCSStaticName, self.Database[Event.IniDCSStaticName] + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] end --- Handles the Database to check on any event that Object exists in the Database. @@ -3254,10 +3254,7 @@ do -- SET_STATIC -- @return #string The name of the STATIC -- @return #table The STATIC function SET_STATIC:FindInDatabase( Event ) - self:F2( { Event.IniDCSStaticName, self.Set[Event.IniDCSStaticName], Event } ) - - - return Event.IniDCSStaticName, self.Set[Event.IniDCSStaticName] + return Event.IniDCSUnitName, self.Set[Event.IniDCSUnitName] end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index fa3e963a5..d88e65a85 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1544,6 +1544,11 @@ function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) -- Return payload. self:ReturnPayloadFromAsset(Asset) + -- Return tacan channel. + if Asset.tacan then + Squadron:ReturnTacan(Asset.tacan) + end + -- Set timestamp. Asset.Treturned=timer.getAbsTime() end @@ -1565,48 +1570,52 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Create a flight group. local flightgroup=self:_CreateFlightGroup(asset) - - -- Set airwing. - flightgroup:SetAirwing(self) + + --- + -- Asset + --- -- Set asset flightgroup. asset.flightgroup=flightgroup - -- Get the SQUADRON of the asset. - local squadron=self:GetSquadronOfAsset(asset) - - -- Set default TACAN channel. - local Tacan=squadron:GetTACAN() - if Tacan then - flightgroup:SetDefaultTACAN(Tacan) - end - - -- Set radio frequency and modulation - local radioFreq, radioModu=squadron:GetRadio() - if radioFreq then - flightgroup:SetDefaultRadio(radioFreq, radioModu) - end - -- Not requested any more. asset.requested=nil -- Did not return yet. - asset.Treturned=nil + asset.Treturned=nil + + --- + -- Squadron + --- + -- Get the SQUADRON of the asset. + local squadron=self:GetSquadronOfAsset(asset) + + -- Set default TACAN channel. + local Tacan=squadron:FetchTacan() + if Tacan then + flightgroup:SwitchTACAN(Tacan, Morse, UnitName, Band) + asset.tacan=Tacan + end + + -- Set radio frequency and modulation + local radioFreq, radioModu=squadron:GetRadio() + if radioFreq then + flightgroup:SwitchRadio(radioFreq, radioModu) + end + -- Set RTB on fuel critical. flightgroup:SetFuelCriticalThreshold() + + --- + -- Mission + --- -- Get Mission (if any). local mission=self:GetMissionByID(request.assignment) -- Add mission to flightgroup queue. if mission then - - -- RTB on low fuel if on GCCAP. - if mission.type==AUFTRAG.Type.GCCAP then - flightgroup:SetFuelLowThreshold(25) - flightgroup:SetFuelLowRTB(true) - end -- Add mission to flightgroup queue. asset.flightgroup:AddMission(mission) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 341727192..2593978ee 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -333,7 +333,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) if self.option.Alarm then self:SwitchAlarmstate(self.option.Alarm) else - self:SwitchAlarmstate(0) + self:SwitchAlarmstate(ENUMS.AlarmState.Auto) end -- Turn TACAN beacon on. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 8c81992c8..5733adf65 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -98,6 +98,9 @@ -- @field #table enrouteTasks Mission enroute tasks. -- -- @field #number repeated Number of times mission was repeated. +-- @field #number repeatedSuccess Number of times mission was repeated after a success. +-- @field #number repeatedFailure Number of times mission was repeated after a failure. +-- @field #number Nrepeat Number of times the mission is repeated. -- @field #number NrepeatFailure Number of times mission is repeated if failed. -- @field #number NrepeatSuccess Number of times mission is repeated if successful. -- @@ -486,6 +489,9 @@ function AUFTRAG:New(Type) self:SetTime() self.engageAsGroup=true self.repeated=0 + self.repeatedSuccess=0 + self.repeatedFailure=0 + self.Nrepeat=0 self.NrepeatFailure=0 self.NrepeatSuccess=0 self.nassets=1 @@ -1415,6 +1421,15 @@ function AUFTRAG:SetPriority(Prio, Urgent, Importance) return self end +--- Set how many times the mission is repeated. Only valid if the mission is handled by an AIRWING or higher level. +-- @param #AUFTRAG self +-- @param #number Nrepeat Number of repeats. Default 0. +-- @return #AUFTRAG self +function AUFTRAG:SetRepeatOnFailure(Nrepeat) + self.Nrepeat=Nrepeat or 0 + return self +end + --- Set how many times the mission is repeated if it fails. Only valid if the mission is handled by an AIRWING or higher level. -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. @@ -2535,8 +2550,9 @@ function AUFTRAG:onafterCancel(From, Event, To) self.Tover=timer.getAbsTime() -- No more repeats. - self.NrepeatFailure=self.repeated - self.NrepeatSuccess=self.repeated + self.Nrepeat=self.repeated + self.NrepeatFailure=self.repeatedFailure + self.NrepeatSuccess=self.repeatedSuccess -- Not necessary to delay the evaluaton?! self.dTevaluate=0 @@ -2583,16 +2599,18 @@ function AUFTRAG:onafterSuccess(From, Event, To) self.status=AUFTRAG.Status.SUCCESS self:T(self.lid..string.format("New mission status=%s", self.status)) - if self.repeated>=self.NrepeatSuccess then + if self.repeatedSuccess>=self.NrepeatSuccess then -- Stop mission. - self:I(self.lid..string.format("Mission SUCCESS! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.repeated, self.NrepeatSuccess)) + self:I(self.lid..string.format("Mission SUCCESS! Number of max repeats reached [%d>=%d] ==> Stopping mission!", self.repeatedSuccess, self.NrepeatSuccess)) self:Stop() else + + self.repeatedSuccess=self.repeatedSuccess+1 -- Repeat mission. - self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, self.NrepeatSuccess)) + self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeatedSuccess, self.NrepeatSuccess)) self:Repeat() end @@ -2609,16 +2627,23 @@ function AUFTRAG:onafterFailed(From, Event, To) self.status=AUFTRAG.Status.FAILED self:T(self.lid..string.format("New mission status=%s", self.status)) - if self.repeated>=self.NrepeatFailure then + local repeatme=self.repeatedFailure=%d] ==> Stopping mission!", self.repeated, self.NrepeatFailure)) - self:Stop() + if self.repeatedFailure>=self.NrepeatFailure then + + self.repeatedFailure=self.repeatedFailure+1 - else + local N=math.max(self.NrepeatFailure, self.Nrepeat) -- Repeat mission. - self:I(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, self.NrepeatFailure)) + self:I(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) self:Repeat() + + + else + + self:I(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1)) + self:Stop() end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 5ac377611..221c09112 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -227,7 +227,9 @@ function FLIGHTGROUP:New(group) -- Defaults self:SetFuelLowThreshold() + self:SetFuelLowRTB() self:SetFuelCriticalThreshold() + self:SetFuelCriticalRTB() self:SetDefaultROE() self:SetDefaultROT() self:SetDetection() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index d6fb149fa..051f31476 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3324,7 +3324,7 @@ function OPSGROUP:TurnOffICLS() end self:I(self.lid..string.format("Switching ICLS OFF")) - self.iclsOn=false + self.icls.On=false end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index b37ce8fe9..e1ff5f966 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -395,6 +395,15 @@ function SQUADRON:SetModex(Modex, Prefix, Suffix) return self end +--- Set low fuel threshold. +-- @param #SQUADRON self +-- @param #number LowFuel Low fuel threshold in percent. Default 25. +-- @return #SQUADRON self +function SQUADRON:SetLowFuelThreshold(LowFuel) + self.fuellow=LowFuel or 25 + return self +end + --- Set airwing. -- @param #SQUADRON self -- @param Ops.AirWing#AIRWING Airwing The airwing. @@ -500,23 +509,33 @@ function SQUADRON:GetModex(Asset) end + +--- Add TACAN channels to the squadron. Note that channels can only range from 1 to 126. +-- @param #SQUADRON self +-- @param #number ChannelMin Channel. +-- @param #number ChannelMax Channel. +-- @return #SQUADRON self +-- @usage mysquad:AddTacanChannel(64,69) -- adds channels 64, 65, 66, 67, 68, 69 +function SQUADRON:AddTacanChannel(ChannelMin, ChannelMax) + + ChannelMax=ChannelMax or ChannelMin + + for i=ChannelMin,ChannelMax do + self.tacanChannel[i]=true + end + +end + --- Get an unused TACAN channel. -- @param #SQUADRON self --- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset. -- @return #number TACAN channel or *nil* if no channel is free. -function SQUADRON:GetTACAN() +function SQUADRON:FetchTacan() - if self.TACANmin and self.TACANmax then - - for channel=self.TACANmin, self.TACANmax do - - if not self.TACANused[channel] then - self.TACANused[channel]=true - return channel - end - + for channel,free in pairs(self.tacanChannel) do + if free then + self.tacanChannel[channel]=false + return channel end - end return nil @@ -525,8 +544,8 @@ end --- "Return" a used TACAN channel. -- @param #SQUADRON self -- @param #number channel The channel that is available again. -function SQUADRON:ReturnTACAN(channel) - self.TACANused[channel]=false +function SQUADRON:ReturnTacan(channel) + self.tacanChannel[channel]=true end --- Check if squadron is "OnDuty". diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index e5b2f3a22..f35f7e6df 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -103,6 +103,7 @@ TARGET.ObjectStatus={ -- @field #number Life Life points on last status update. -- @field #number Life0 Life points of completely healthy target. -- @field #string Status Status "Alive" or "Dead". +-- @field Core.Point#COORDINATE Coordinate of the target object. --- Global target ID counter. _TARGETID=0 @@ -436,7 +437,9 @@ function TARGET:_AddObject(Object) local group=Object --Wrapper.Group#GROUP target.Type=TARGET.ObjectType.GROUP - target.Name=group:GetName() + target.Name=group:GetName() + + target.Coordinate=group:GetCoordinate() local units=group:GetUnits() @@ -461,6 +464,8 @@ function TARGET:_AddObject(Object) target.Type=TARGET.ObjectType.UNIT target.Name=unit:GetName() + target.Coordinate=unit:GetCoordinate() + if unit and unit:IsAlive() then target.Life=unit:GetLife() target.Life0=math.max(unit:GetLife0(), target.Life) -- There was an issue with ships that life is greater life0! @@ -477,6 +482,8 @@ function TARGET:_AddObject(Object) target.Type=TARGET.ObjectType.STATIC target.Name=static:GetName() + target.Coordinate=static:GetCoordinate() + if static and static:IsAlive() then target.Life0=1 target.Life=1 @@ -491,6 +498,8 @@ function TARGET:_AddObject(Object) target.Type=TARGET.ObjectType.AIRBASE target.Name=airbase:GetName() + + target.Coordinate=airbase:GetCoordinate() target.Life0=1 target.Life=1 @@ -503,6 +512,8 @@ function TARGET:_AddObject(Object) target.Type=TARGET.ObjectType.COORDINATE target.Name=coord:ToStringMGRS() + + target.Coordinate=Object target.Life0=1 target.Life=1 @@ -621,6 +632,56 @@ function TARGET:GetLife() return N end +--- Get target 3D position vector. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return DCS#Vec3 Vector with x,y,z components +function TARGET:GetTargetVec3(Target) + + if Target.Type==TARGET.ObjectType.GROUP then + + local object=Target.Object --Wrapper.Group#GROUP + + if object and object:IsAlive() then + + return object:GetVec3() + + end + + elseif Target.Type==TARGET.ObjectType.UNIT then + + local object=Target.Object --Wrapper.Unit#UNIT + + if object and object:IsAlive() then + return object:GetVec3() + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + local object=Target.Object --Wrapper.Static#STATIC + + if object and object:IsAlive() then + return Target.Object:GetCoordinate() + end + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + local object=Target.Object --Wrapper.Airbase#AIRBASE + + return object:GetVec3() + + --if Target.Status==TARGET.ObjectStatus.ALIVE then + --end + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + local object=Target.Object --Core.Point#COORDINATE + + return {x=object.x, y=object.y, z=object.z} + + end + +end --- Get target coordinate. @@ -629,36 +690,25 @@ end -- @return Core.Point#COORDINATE Coordinate of the target. function TARGET:GetTargetCoordinate(Target) - if Target.Type==TARGET.ObjectType.GROUP then - - if Target.Object and Target.Object:IsAlive() then - - return Target.Object:GetCoordinate() - - end - - elseif Target.Type==TARGET.ObjectType.UNIT then - - if Target.Object and Target.Object:IsAlive() then - return Target.Object:GetCoordinate() - end - - elseif Target.Type==TARGET.ObjectType.STATIC then - - if Target.Object and Target.Object:IsAlive() then - return Target.Object:GetCoordinate() - end - - elseif Target.Type==TARGET.ObjectType.AIRBASE then - - if Target.Status==TARGET.ObjectStatus.ALIVE then - return Target.Object:GetCoordinate() - end - - elseif Target.Type==TARGET.ObjectType.COORDINATE then + if Target.Type==TARGET.ObjectType.COORDINATE then + -- Coordinate is the object itself. return Target.Object + else + + -- Get updated position vector. + local vec3=self:GetTargetVec3(Target) + + -- Update position. This saves us to create a new COORDINATE object each time. + if vec3 then + Target.Coordinate.x=vec3.x + Target.Coordinate.y=vec3.y + Target.Coordinate.z=vec3.z + end + + return Target.Coordinate + end return nil diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 82b0ea1c7..fda0abe58 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -261,7 +261,9 @@ end -- @param #string GroupName The Group name -- @return #GROUP self function GROUP:Register( GroupName ) + local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) -- #GROUP + self.GroupName = GroupName self:SetEventPriority( 4 ) From dee7307adc693f696f73d0bbf47799b897421d4a Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 19 Aug 2020 01:06:28 +0200 Subject: [PATCH 42/79] Ops --- Moose Development/Moose/Ops/AirWing.lua | 5 +-- Moose Development/Moose/Ops/Auftrag.lua | 32 ++++++++++++------- Moose Development/Moose/Ops/FlightGroup.lua | 1 + Moose Development/Moose/Ops/OpsGroup.lua | 23 +++++-------- Moose Development/Moose/Ops/Squadron.lua | 10 ++---- .../Moose/Utilities/Profiler.lua | 23 ++++++++++--- 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index d88e65a85..52214c1f2 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1604,8 +1604,9 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) flightgroup:SwitchRadio(radioFreq, radioModu) end - -- Set RTB on fuel critical. - flightgroup:SetFuelCriticalThreshold() + if squadron.fuellow then + flightgroup:SetFuelCriticalThreshold(squadron.fuellow) + end --- -- Mission diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 5733adf65..e3345c937 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1425,7 +1425,7 @@ end -- @param #AUFTRAG self -- @param #number Nrepeat Number of repeats. Default 0. -- @return #AUFTRAG self -function AUFTRAG:SetRepeatOnFailure(Nrepeat) +function AUFTRAG:SetRepeat(Nrepeat) self.Nrepeat=Nrepeat or 0 return self end @@ -2599,19 +2599,25 @@ function AUFTRAG:onafterSuccess(From, Event, To) self.status=AUFTRAG.Status.SUCCESS self:T(self.lid..string.format("New mission status=%s", self.status)) - if self.repeatedSuccess>=self.NrepeatSuccess then + local repeatme=self.repeatedFailure=%d] ==> Stopping mission!", self.repeatedSuccess, self.NrepeatSuccess)) - self:Stop() + if repeatme then + + -- Increase counter. + self.repeatedSuccess=self.repeatedSuccess+1 + + -- Number of repeats. + local N=math.max(self.NrepeatSuccess, self.Nrepeat) + + -- Repeat mission. + self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) + self:Repeat() else - self.repeatedSuccess=self.repeatedSuccess+1 - - -- Repeat mission. - self:I(self.lid..string.format("Mission SUCCESS! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeatedSuccess, self.NrepeatSuccess)) - self:Repeat() + -- Stop mission. + self:I(self.lid..string.format("Mission SUCCESS! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1)) + self:Stop() end @@ -2629,19 +2635,21 @@ function AUFTRAG:onafterFailed(From, Event, To) local repeatme=self.repeatedFailure=self.NrepeatFailure then + if repeatme then + -- Increase counter. self.repeatedFailure=self.repeatedFailure+1 + -- Number of repeats. local N=math.max(self.NrepeatFailure, self.Nrepeat) -- Repeat mission. self:I(self.lid..string.format("Mission FAILED! Repeating mission for the %d time (max %d times) ==> Repeat mission!", self.repeated+1, N)) self:Repeat() - else + -- Stop mission. self:I(self.lid..string.format("Mission FAILED! Number of max repeats %d reached ==> Stopping mission!", self.repeated+1)) self:Stop() diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 221c09112..b3ecb9298 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -226,6 +226,7 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults + self:SetVerbosity(3) self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 051f31476..6b64b3d73 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -3227,22 +3227,15 @@ function OPSGROUP:TurnOffTACAN() end - ---- Set default ICLS parameters. +--- Get current TACAN parameters. -- @param #OPSGROUP self --- @param #number Channel ICLS channel. --- @param #string Morse Morse code. Default "XXX". --- @param #string UnitName Name of the unit acting as beacon. --- @param #string Band ICLS mode. Default is "X" for ground and "Y" for airborne units. --- @return #OPSGROUP self -function OPSGROUP:SetDefaultICLS(Channel, Morse, UnitName) - - self.iclsDefault={} - self.iclsDefault.Channel=Channel - self.iclsDefault.Morse=Morse or "XXX" - self.iclsDefault.BeaconName=UnitName - - return self +-- @return #number TACAN channel. +-- @return #string TACAN Morse code. +-- @return #string TACAN band ("X" or "Y"). +-- @return #boolean TACAN is On (true) or Off (false). +-- @return #string UnitName Name of the unit acting as beacon. +function OPSGROUP:GetTACAN() + return self.tacan.Channel, self.tacan.Morse, self.tacan.Band, self.tacan.On, self.tacan.BeaconName end --- Activate/switch ICLS beacon settings. diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index e1ff5f966..2b7bdbc4f 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -41,9 +41,7 @@ -- @field #string attribute Generalized attribute of the squadron template group. -- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil. -- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probpe=1). Default nil. --- @field #number TACANmin TACAN min channel. --- @field #number TACANmax TACAN max channel. --- @field #table TACANused Table of used TACAN channels. +-- @field #table tacanChannel List of TACAN channels available to the squadron. -- @field #number radioFreq Radio frequency in MHz the squad uses. -- @field #number radioModu Radio modulation the squad uses. -- @extends Core.Fsm#FSM @@ -84,9 +82,7 @@ SQUADRON = { engageRange = nil, tankerSystem = nil, refuelSystem = nil, - TACANmin = nil, - TACANmax = nil, - TACANused = {}, + tacanChannel = {}, } --- SQUADRON class version. @@ -399,7 +395,7 @@ end -- @param #SQUADRON self -- @param #number LowFuel Low fuel threshold in percent. Default 25. -- @return #SQUADRON self -function SQUADRON:SetLowFuelThreshold(LowFuel) +function SQUADRON:SetFuelLowThreshold(LowFuel) self.fuellow=LowFuel or 25 return self end diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 639e7174a..c1667bc3b 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -289,10 +289,10 @@ function PROFILER.showTable(data, f, runTimeGame) -- Calls per second. local cps=t.count/runTimeGame - if (cps>=PROFILER.lowCpsThres) then + if cps>=PROFILER.lowCpsThres then -- Output - local text=string.format("%30s: %8d calls %8.1f/sec - Time %8.3f sec (%.3f %%) %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, tostring(t.src), tostring(t.line)) + local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) - Per call %5.3f sec %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) PROFILER._flog(f, text) end @@ -455,6 +455,19 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) PROFILER._flog(f,"************************************************************************************************************************") PROFILER._flog(f,"") PROFILER.showTable(t, f, runTimeGame) + + -- Sort by number of calls. + table.sort(t, function(a,b) return a.tm/a.count>b.tm/b.count end) + + -- Detailed data. + PROFILER._flog(f,"") + PROFILER._flog(f,"************************************************************************************************************************") + PROFILER._flog(f,"") + PROFILER._flog(f,"--------------------------------------") + PROFILER._flog(f,"---- Data Sorted by Time per Call ----") + PROFILER._flog(f,"--------------------------------------") + PROFILER._flog(f,"") + PROFILER.showTable(t, f, runTimeGame) -- Sort by number of calls. table.sort(t, function(a,b) return a.count>b.count end) @@ -463,9 +476,9 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) PROFILER._flog(f,"") PROFILER._flog(f,"************************************************************************************************************************") PROFILER._flog(f,"") - PROFILER._flog(f,"------------------------------") - PROFILER._flog(f,"---- Data Sorted by Calls ----") - PROFILER._flog(f,"------------------------------") + PROFILER._flog(f,"------------------------------------") + PROFILER._flog(f,"---- Data Sorted by Total Calls ----") + PROFILER._flog(f,"------------------------------------") PROFILER._flog(f,"") PROFILER.showTable(t, f, runTimeGame) From f4bfcf58fd0285e8861ee98084d770af627ceeda Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 19 Aug 2020 22:32:16 +0200 Subject: [PATCH 43/79] Ops --- Moose Development/Moose/Ops/AirWing.lua | 8 +- Moose Development/Moose/Ops/FlightGroup.lua | 20 +++-- Moose Development/Moose/Ops/OpsGroup.lua | 33 +++++++++ Moose Development/Moose/Ops/Squadron.lua | 2 + .../Moose/Utilities/Profiler.lua | 24 +++--- Moose Development/Moose/Wrapper/Airbase.lua | 74 +++++++++++++++++++ 6 files changed, 137 insertions(+), 24 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 52214c1f2..b6dc7ffc3 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1748,7 +1748,11 @@ function AIRWING:_CreateFlightGroup(asset) local flightgroup=FLIGHTGROUP:New(asset.spawngroupname) -- Set airwing. - flightgroup:SetAirwing(self) + flightgroup:SetAirwing(self) + + flightgroup.squadron=self:GetSquadronOfAsset(asset) + + --[[ --- Check if out of missiles. For A2A missions ==> RTB. function flightgroup:OnAfterOutOfMissiles() @@ -1774,6 +1778,8 @@ function AIRWING:_CreateFlightGroup(asset) end + ]] + return flightgroup end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index b3ecb9298..1ae0ec45e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -34,6 +34,7 @@ -- @field #boolean fuelcritical Fuel critical switch. -- @field #number fuelcriticalthresh Critical fuel threshold in percent. -- @field #boolean fuelcriticalrtb RTB on critical fuel switch. +-- @field Ops.Squadron#SQUADRON squadron The squadron of this flight group. -- @field Ops.AirWing#AIRWING airwing The airwing the flight group belongs to. -- @field Ops.FlightControl#FLIGHTCONTROL flightcontrol The flightcontrol handling this group. -- @field Ops.Airboss#AIRBOSS airboss The airboss handling this group. @@ -2468,9 +2469,11 @@ function FLIGHTGROUP:_InitGroup() self:E(self.lid.."WARNING: Group was already initialized!") return end + + local group=self.group --Wrapper.Group#GROUP -- Get template of group. - self.template=self.group:GetTemplate() + self.template=group:GetTemplate() -- Define category. self.isAircraft=true @@ -2478,7 +2481,7 @@ function FLIGHTGROUP:_InitGroup() self.isGround=false -- Helo group. - self.ishelo=self.group:IsHelicopter() + self.ishelo=group:IsHelicopter() -- Is (template) group uncontrolled. self.isUncontrolled=self.template.uncontrolled @@ -2487,7 +2490,7 @@ function FLIGHTGROUP:_InitGroup() self.isLateActivated=self.template.lateActivation -- Max speed in km/h. - self.speedmax=self.group:GetSpeedMax() + self.speedmax=group:GetSpeedMax() -- Cruise speed limit 350 kts for fixed and 80 knots for rotary wings. local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) @@ -2525,7 +2528,7 @@ function FLIGHTGROUP:_InitGroup() end -- Is this purely AI? - self.ai=not self:_IsHuman(self.group) + self.ai=not self:_IsHuman(group) -- Create Menu. if not self.ai then @@ -2607,10 +2610,11 @@ function FLIGHTGROUP:AddElementByName(unitname) -- TODO: this is wrong when grouping is used! local unittemplate=element.unit:GetTemplate() - element.modex=element.unit:GetTemplate().onboard_num - element.skill=element.unit:GetTemplate().skill - element.pylons=element.unit:GetTemplatePylons() - element.fuelmass0=element.unit:GetTemplatePayload().fuel + element.modex=unittemplate.onboard_num + element.skill=unittemplate.skill + element.payload=unittemplate.payload + element.pylons=unittemplate.payload and unittemplate.payload.pylons or nil --element.unit:GetTemplatePylons() + element.fuelmass0=unittemplate.payload and unittemplate.payload.fuel or 0 --element.unit:GetTemplatePayload().fuel element.fuelmass=element.fuelmass0 element.fuelrel=element.unit:GetFuel() element.category=element.unit:GetUnitCategory() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 6b64b3d73..229daab70 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2052,6 +2052,34 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 AIRWING.UpdatePatrolPointMarker(Mission.patroldata) end + + env.info("FF 000") + + -- TACAN + if Mission.tacan then + + env.info("FF 100") + + if self.tacanDefault then + env.info("FF 200") + self:_SwitchTACAN(self.tacanDefault) + else + env.info("FF 300") + self:TurnOffTACAN() + end + + local squadron=self.squadron --Ops.Squadron#SQUADRON + if squadron then + env.info("FF 400") + squadron:ReturnTacan(Mission.tacan.Channel) + end + + local asset=Mission:GetAssetByName(self.groupname) + if asset then + env.info("FF 500") + asset.tacan=nil + end + end -- TODO: reset mission specific parameters like radio, ROE etc. @@ -3183,6 +3211,11 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) -- Tacan frequency. local Frequency=UTILS.TACANToFrequency(Channel, Band) + -- Backup TACAN. + if self.tacan.Channel then + self.tacanDefault=UTILS.DeepCopy(self.tacan) + end + -- Update info. self.tacan.Channel=Channel self.tacan.Morse=Morse diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 2b7bdbc4f..5f446d3e5 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -529,6 +529,7 @@ function SQUADRON:FetchTacan() for channel,free in pairs(self.tacanChannel) do if free then + self:I(self.lid..string.format("Checking out Tacan channel %d", channel)) self.tacanChannel[channel]=false return channel end @@ -541,6 +542,7 @@ end -- @param #SQUADRON self -- @param #number channel The channel that is available again. function SQUADRON:ReturnTacan(channel) + self:I(self.lid..string.format("Returning Tacan channel %d", channel)) self.tacanChannel[channel]=true end diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index c1667bc3b..76fee9dcc 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -111,16 +111,20 @@ PROFILER = { -- @param #number Duration Duration in (game) seconds before the profiler is stopped. Default is when mission ends. function PROFILER.Start(Delay, Duration) - -- Check if os and lfs are available. + -- Check if os, io and lfs are available. local go=true if not os then - error("Profiler needs os to be desanitized") + env.error("ERROR: Profiler needs os to be desanitized!") go=false end + if not io then + env.error("ERROR: Profiler needs io to be desanitized!") + go=false + end if not lfs then - error("Profiler needs lfs to be desanitized") + env.error("ERROR: Profiler needs lfs to be desanitized!") go=false - end + end if not go then return end @@ -136,16 +140,6 @@ function PROFILER.Start(Delay, Duration) -- Add event handler. world.addEventHandler(PROFILER.eventHandler) - --[[ - -- Message to screen. - local function showProfilerRunning() - timer.scheduleFunction(showProfilerRunning, nil, timer.getTime()+600) - trigger.action.outText("### Profiler running ###", 600) - end - -- Message. - showProfilerRunning() - ]] - -- Info in log. env.info('############################ Profiler Started ############################') if Duration then @@ -292,7 +286,7 @@ function PROFILER.showTable(data, f, runTimeGame) if cps>=PROFILER.lowCpsThres then -- Output - local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) - Per call %5.3f sec %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) + local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) PROFILER._flog(f, text) end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 9c5675c53..234a92533 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -315,6 +315,80 @@ AIRBASE.TheChannel = { ["High_Halden"] = "High Halden", } +--- Airbases of Syria +-- +-- * AIRBASE.Syria.Kuweires +-- * AIRBASE.Syria.Marj_Ruhayyil +-- * AIRBASE.Syria.Kiryat_Shmona +-- * AIRBASE.Syria.Marj_as_Sultan_North +-- * AIRBASE.Syria.Eyn_Shemer +-- * AIRBASE.Syria.Incirlik +-- * AIRBASE.Syria.Damascus +-- * AIRBASE.Syria.Bassel_Al_Assad +-- * AIRBASE.Syria.Aleppo +-- * AIRBASE.Syria.Qabr_as_Sitt +-- * AIRBASE.Syria.Wujah_Al_Hajar +-- * AIRBASE.Syria.Al_Dumayr +-- * AIRBASE.Syria.Hatay +-- * AIRBASE.Syria.Haifa +-- * AIRBASE.Syria.Khalkhalah +-- * AIRBASE.Syria.Megiddo +-- * AIRBASE.Syria.Rayak +-- * AIRBASE.Syria.Mezzeh +-- * AIRBASE.Syria.King_Hussein_Air_College +-- * AIRBASE.Syria.Jirah +-- * AIRBASE.Syria.Taftanaz +-- * AIRBASE.Syria.Rene_Mouawad +-- * AIRBASE.Syria.Ramat_David +-- * AIRBASE.Syria.Minakh +-- * AIRBASE.Syria.Adana_Sakirpasa +-- * AIRBASE.Syria.Marj_as_Sultan_South +-- * AIRBASE.Syria.Hama +-- * AIRBASE.Syria.Al_Qusayr +-- * AIRBASE.Syria.Palmyra +-- * AIRBASE.Syria.Tabqa +-- * AIRBASE.Syria.Beirut_Rafic_Hariri +-- * AIRBASE.Syria.An_Nasiriyah +-- * AIRBASE.Syria.Abu_al_Duhur +-- +-- @field Syria +AIRBASE.Syria={ + ["Kuweires"]="Kuweires", + ["Marj_Ruhayyil"]="Marj Ruhayyil", + ["Kiryat_Shmona"]="Kiryat Shmona", + ["Marj_as_Sultan_North"]="Marj as Sultan North", + ["Eyn_Shemer"]="Eyn Shemer", + ["Incirlik"]="Incirlik", + ["Damascus"]="Damascus", + ["Bassel_Al_Assad"]="Bassel Al-Assad", + ["Aleppo"]="Aleppo", + ["Qabr_as_Sitt"]="Qabr as Sitt", + ["Wujah_Al_Hajar"]="Wujah Al Hajar", + ["Al_Dumayr"]="Al-Dumayr", + ["Hatay"]="Hatay", + ["Haifa"]="Haifa", + ["Khalkhalah"]="Khalkhalah", + ["Megiddo"]="Megiddo", + ["Rayak"]="Rayak", + ["Mezzeh"]="Mezzeh", + ["King_Hussein_Air_College"]="King Hussein Air College", + ["Jirah"]="Jirah", + ["Taftanaz"]="Taftanaz", + ["Rene_Mouawad"]="Rene Mouawad", + ["Ramat_David"]="Ramat David", + ["Minakh"]="Minakh", + ["Adana_Sakirpasa"]="Adana Sakirpasa", + ["Marj_as_Sultan_South"]="Marj as Sultan South", + ["Hama"]="Hama", + ["Al_Qusayr"]="Al Qusayr", + ["Palmyra"]="Palmyra", + ["Tabqa"]="Tabqa", + ["Beirut_Rafic_Hariri"]="Beirut-Rafic Hariri", + ["An_Nasiriyah"]="An Nasiriyah", + ["Abu_al_Duhur"]="Abu al-Duhur", +} + + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. From 61adeeeda365936c9ea23e970350dc584c79d554 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 20 Aug 2020 01:24:28 +0200 Subject: [PATCH 44/79] Delete Astar - Copy.lua --- Moose Development/Moose/Core/Astar - Copy.lua | 932 ------------------ 1 file changed, 932 deletions(-) delete mode 100644 Moose Development/Moose/Core/Astar - Copy.lua diff --git a/Moose Development/Moose/Core/Astar - Copy.lua b/Moose Development/Moose/Core/Astar - Copy.lua deleted file mode 100644 index de590b07c..000000000 --- a/Moose Development/Moose/Core/Astar - Copy.lua +++ /dev/null @@ -1,932 +0,0 @@ ---- **Core** - A* Pathfinding. --- --- **Main Features:** --- --- * Find path from A to B. --- * Pre-defined as well as custom valid neighbour functions. --- * Pre-defined as well as custom cost functions. --- * Easy rectangular grid setup. --- --- === --- --- ### Author: **funkyfranky** --- @module Core.Astar --- @image CORE_Astar.png - - ---- ASTAR class. --- @type ASTAR --- @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 #table nodes Table of nodes. --- @field #number counter Node counter. --- @field #ASTAR.Node startNode Start node. --- @field #ASTAR.Node endNode End node. --- @field Core.Point#COORDINATE startCoord Start coordinate. --- @field Core.Point#COORDINATE endCoord End coordinate. --- @field #function ValidNeighbourFunc Function to check if a node is valid. --- @field #table ValidNeighbourArg Optional arguments passed to the valid neighbour function. --- @field #function CostFunc Function to calculate the heuristic "cost" to go from one node to another. --- @field #table CostArg Optional arguments passed to the cost function. --- @extends Core.Base#BASE - ---- When nothing goes right... Go left! --- --- === --- --- ![Banner Image](..\Presentations\Astar\ASTAR_Main.jpg) --- --- # The ASTAR Concept --- --- Pathfinding algorithm. --- --- --- # Start and Goal --- --- The first thing we need to define is obviously the place where we want to start and where we want to go eventually. --- --- ## Start --- --- The start --- --- ## Goal --- --- --- # Nodes --- --- ## Rectangular Grid --- --- A rectangular grid can be created using the @{#ASTAR.CreateGrid}(*ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid*), where --- --- * *ValidSurfaceTypes* is a table of valid surface types. By default all surface types are valid. --- * *BoxXY* is the width of the grid perpendicular the the line between start and end node. Default is 40,000 meters (40 km). --- * *SpaceX* is the additional space behind the start and end nodes. Default is 20,000 meters (20 km). --- * *deltaX* is the grid spacing between nodes in the direction of start and end node. Default is 2,000 meters (2 km). --- * *deltaY* is the grid spacing perpendicular to the direction of start and end node. Default is the same as *deltaX*. --- * *MarkGrid* If set to *true*, this places marker on the F10 map on each grid node. Note that this can stall DCS if too many nodes are created. --- --- ## Valid Surfaces --- --- Certain unit types can only travel on certain surfaces types, for example --- --- * Naval units can only travel on water (that also excludes shallow water in DCS currently), --- * Ground units can only traval on land. --- --- By restricting the surface type in the grid construction, we also reduce the number of nodes, which makes the algorithm more efficient. --- --- ## Box Width (BoxHY) --- --- The box width needs to be large enough to capture all paths you want to consider. --- --- ## Space in X --- --- The space in X value is important if the algorithm needs to to backwards from the start node or needs to extend even further than the end node. --- --- ## Grid Spacing --- --- The grid spacing is an important factor as it determines the number of nodes and hence the performance of the algorithm. It should be as large as possible. --- However, if the value is too large, the algorithm might fail to get a valid path. --- --- A good estimate of the grid spacing is to set it to be smaller (~ half the size) of the smallest gap you need to path. --- --- # Valid Neighbours --- --- The A* algorithm needs to know if a transition from one node to another is allowed or not. By default, hopping from one node to another is always possible. --- --- ## Line of Sight --- --- For naval --- --- --- # Heuristic Cost --- --- In order to determine the optimal path, the pathfinding algorithm needs to know, how costly it is to go from one node to another. --- Often, this can simply be determined by the distance between two nodes. Therefore, the default cost function is set to be the 2D distance between two nodes. --- --- --- # Calculate the Path --- --- Finally, we have to calculate the path. This is done by the @{ASTAR.GetPath}(*ExcludeStart, ExcludeEnd*) function. This function returns a table of nodes, which --- describe the optimal path from the start node to the end node. --- --- By default, the start and end node are include in the table that is returned. --- --- Note that a valid path must not always exist. So you should check if the function returns *nil*. --- --- Common reasons that a path cannot be found are: --- --- * The grid is too small ==> increase grid size, e.g. *BoxHY* and/or *SpaceX* if you use a rectangular grid. --- * The grid spacing is too large ==> decrease *deltaX* and/or *deltaY* --- * There simply is no valid path ==> you are screwed :( --- --- --- # Examples --- --- ## Strait of Hormuz --- --- Carrier Group finds its way through the Stait of Hormuz. --- --- ## --- --- --- --- @field #ASTAR -ASTAR = { - ClassName = "ASTAR", - Debug = nil, - lid = nil, - nodes = {}, - counter = 1, -} - ---- Node data. --- @type ASTAR.Node --- @field #number id Node id. --- @field Core.Point#COORDINATE coordinate Coordinate of the node. --- @field #number surfacetype Surface type. --- @field #table valid Cached valid/invalid nodes. --- @field #table cost Cached cost. - ---- ASTAR infinity. --- @field #number INF -ASTAR.INF=1/0 - ---- ASTAR class version. --- @field #string version -ASTAR.version="0.3.0" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO list -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- TODO: Add more valid neighbour functions. --- TODO: Write docs. - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructor -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new ASTAR object. --- @param #ASTAR self --- @return #ASTAR self -function ASTAR:New() - - -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, BASE:New()) --#ASTAR - - self.lid="ASTAR | " - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set coordinate from where to start. --- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate Start coordinate. --- @return #ASTAR self -function ASTAR:SetStartCoordinate(Coordinate) - - self.startCoord=Coordinate - - return self -end - ---- Set coordinate where you want to go. --- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate end coordinate. --- @return #ASTAR self -function ASTAR:SetEndCoordinate(Coordinate) - - self.endCoord=Coordinate - - return self -end - ---- Create a node from a given coordinate. --- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate The coordinate where to create the node. --- @return #ASTAR.Node The node. -function ASTAR:GetNodeFromCoordinate(Coordinate) - - local node={} --#ASTAR.Node - - node.coordinate=Coordinate - node.surfacetype=Coordinate:GetSurfaceType() - node.id=self.counter - - node.valid={} - node.cost={} - - self.counter=self.counter+1 - - return node -end - - ---- Add a node to the table of grid nodes. --- @param #ASTAR self --- @param #ASTAR.Node Node The node to be added. --- @return #ASTAR self -function ASTAR:AddNode(Node) - - table.insert(self.nodes, Node) - - return self -end - ---- Add a node to the table of grid nodes specifying its coordinate. --- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate The coordinate where the node is created. --- @return #ASTAR.Node The node. -function ASTAR:AddNodeFromCoordinate(Coordinate) - - local node=self:GetNodeFromCoordinate(Coordinate) - - self:AddNode(node) - - return node -end - ---- Check if the coordinate of a node has is at a valid surface type. --- @param #ASTAR self --- @param #ASTAR.Node Node The node to be added. --- @param #table SurfaceTypes Surface types, for example `{land.SurfaceType.WATER}`. By default all surface types are valid. --- @return #boolean If true, surface type of node is valid. -function ASTAR:CheckValidSurfaceType(Node, SurfaceTypes) - - if SurfaceTypes then - - if type(SurfaceTypes)~="table" then - SurfaceTypes={SurfaceTypes} - end - - for _,surface in pairs(SurfaceTypes) do - if surface==Node.surfacetype then - return true - end - end - - return false - - else - return true - end - -end - ---- Add a function to determine if a neighbour of a node is valid. --- @param #ASTAR self --- @param #function NeighbourFunction Function that needs to return *true* for a neighbour to be valid. --- @param ... Condition function arguments if any. --- @return #ASTAR self -function ASTAR:SetValidNeighbourFunction(NeighbourFunction, ...) - - self.ValidNeighbourFunc=NeighbourFunction - - self.ValidNeighbourArg={} - if arg then - self.ValidNeighbourArg=arg - end - - return self -end - - ---- Set valid neighbours to require line of sight between two nodes. --- @param #ASTAR self --- @param #number CorridorWidth Width of LoS corridor in meters. --- @return #ASTAR self -function ASTAR:SetValidNeighbourLoS(CorridorWidth) - - self:SetValidNeighbourFunction(ASTAR.LoS, CorridorWidth) - - return self -end - ---- Set valid neighbours to be in a certain distance. --- @param #ASTAR self --- @param #number MaxDistance Max distance between nodes in meters. Default is 2000 m. --- @return #ASTAR self -function ASTAR:SetValidNeighbourDistance(MaxDistance) - - self:SetValidNeighbourFunction(ASTAR.DistMax, MaxDistance) - - return self -end - ---- Set the function which calculates the "cost" to go from one to another node. --- The first to arguments of this function are always the two nodes under consideration. But you can add optional arguments. --- Very often the distance between nodes is a good measure for the cost. --- @param #ASTAR self --- @param #function CostFunction Function that returns the "cost". --- @param ... Condition function arguments if any. --- @return #ASTAR self -function ASTAR:SetCostFunction(CostFunction, ...) - - self.CostFunc=CostFunction - - self.CostArg={} - if arg then - self.CostArg=arg - end - - return self -end - ---- Set heuristic cost to go from one node to another to be their 2D distance. --- @param #ASTAR self --- @return #ASTAR self -function ASTAR:SetCostDist2D() - - self:SetCostFunction(ASTAR.Dist2D) - - return self -end - ---- Set heuristic cost to go from one node to another to be their 3D distance. --- @param #ASTAR self --- @return #ASTAR self -function ASTAR:SetCostDist3D() - - self:SetCostFunction(ASTAR.Dist3D) - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Grid functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a rectangular grid of nodes between star and end coordinate. --- The coordinate system is oriented along the line between start and end point. --- @param #ASTAR self --- @param #table ValidSurfaceTypes Valid surface types. By default is all surfaces are allowed. --- @param #number BoxHY Box "height" in meters along the y-coordinate. Default 40000 meters (40 km). --- @param #number SpaceX Additional space in meters before start and after end coordinate. Default 10000 meters (10 km). --- @param #number deltaX Increment in the direction of start to end coordinate in meters. Default 2000 meters. --- @param #number deltaY Increment perpendicular to the direction of start to end coordinate in meters. Default is same as deltaX. --- @param #boolean MarkGrid If true, create F10 map markers at grid nodes. --- @return #ASTAR self -function ASTAR:CreateGrid(ValidSurfaceTypes, BoxHY, SpaceX, deltaX, deltaY, MarkGrid) - - -- Note that internally - -- x coordinate is z: x-->z Line from start to end - -- y coordinate is x: y-->x Perpendicular - - -- Grid length and width. - local Dz=SpaceX or 10000 - local Dx=BoxHY and BoxHY/2 or 20000 - - -- Increments. - local dz=deltaX or 2000 - local dx=deltaY or dz - - -- Heading from start to end coordinate. - local angle=self.startCoord:HeadingTo(self.endCoord) - - --Distance between start and end. - local dist=self.startCoord:Get2DDistance(self.endCoord)+2*Dz - - -- Origin of map. Needed to translate back to wanted position. - local co=COORDINATE:New(0, 0, 0) - local do1=co:Get2DDistance(self.startCoord) - local ho1=co:HeadingTo(self.startCoord) - - -- Start of grid. - local xmin=-Dx - local zmin=-Dz - - -- Number of grid points. - local nz=dist/dz+1 - local nx=2*Dx/dx+1 - - -- Debug info. - local text=string.format("Building grid with nx=%d ny=%d => total=%d nodes", nx, nz, nx*nz) - self:I(self.lid..text) - MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) - - - -- Loop over x and z coordinate to create a 2D grid. - for i=1,nx do - - -- x coordinate perpendicular to z. - local x=xmin+dx*(i-1) - - for j=1,nz do - - -- z coordinate connecting start and end. - local z=zmin+dz*(j-1) - - -- Rotate 2D. - local vec3=UTILS.Rotate2D({x=x, y=0, z=z}, angle) - - -- Coordinate of the node. - local c=COORDINATE:New(vec3.z, vec3.y, vec3.x):Translate(do1, ho1, true) - - -- Create a node at this coordinate. - local node=self:GetNodeFromCoordinate(c) - - -- Check if node has valid surface type. - if self:CheckValidSurfaceType(node, ValidSurfaceTypes) then - - if MarkGrid then - c:MarkToAll(string.format("i=%d, j=%d surface=%d", i, j, node.surfacetype)) - end - - -- Add node to grid. - self:AddNode(node) - - end - - end - end - - -- Debug info. - local text=string.format("Done building grid!") - self:I(self.lid..text) - MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Valid neighbour functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Function to check if two nodes have line of sight (LoS). --- @param #ASTAR.Node nodeA First node. --- @param #ASTAR.Node nodeB Other node. --- @param #number corridor (Optional) Width of corridor in meters. --- @return #boolean If true, two nodes have LoS. -function ASTAR.LoS(nodeA, nodeB, corridor) - - local offset=0.1 - - local dx=corridor and corridor/2 or nil - local dy=dx - - local cA=nodeA.coordinate:GetVec3() - local cB=nodeB.coordinate:GetVec3() - cA.y=offset - cB.y=offset - - local los=land.isVisible(cA, cB) - - if los and corridor then - - -- Heading from A to B. - local heading=nodeA.coordinate:HeadingTo(nodeB.coordinate) - - local Ap=UTILS.VecTranslate(cA, dx, heading+90) - local Bp=UTILS.VecTranslate(cB, dx, heading+90) - - los=land.isVisible(Ap, Bp) --Ap:IsLOS(Bp, offset) - - if los then - - local Am=UTILS.VecTranslate(cA, dx, heading-90) - local Bm=UTILS.VecTranslate(cB, dx, heading-90) - - los=land.isVisible(Am, Bm) - end - - end - - return los -end - ---- Function to check if two nodes have line of sight (LoS). --- @param #ASTAR.Node nodeA First node. --- @param #ASTAR.Node nodeB Other node. --- @param #number distmax Max distance in meters. Default is 2000 m. --- @return #boolean If true, distance between the two nodes is below threshold. -function ASTAR.DistMax(nodeA, nodeB, distmax) - - distmax=distmax or 2000 - - local dist=nodeA.coordinate:Get2DDistance(nodeB.coordinate) - - return dist<=distmax -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Heuristic cost functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Heuristic cost is given by the 2D distance between the nodes. --- @param #ASTAR.Node nodeA First node. --- @param #ASTAR.Node nodeB Other node. --- @return #number Distance between the two nodes. -function ASTAR.Dist2D(nodeA, nodeB) - return nodeA.coordinate:Get2DDistance(nodeB) -end - ---- Heuristic cost is given by the 3D distance between the nodes. --- @param #ASTAR.Node nodeA First node. --- @param #ASTAR.Node nodeB Other node. --- @return #number Distance between the two nodes. -function ASTAR.Dist3D(nodeA, nodeB) - return nodeA.coordinate:Get3DDistance(nodeB.coordinate) -end - ---- Heuristic cost is given by the distance between the nodes on road. --- @param #ASTAR.Node nodeA First node. --- @param #ASTAR.Node nodeB Other node. --- @return #number Distance between the two nodes. -function ASTAR.DistRoad(nodeA, nodeB) - - local path,dist,gotpath=nodeA.coordinate:GetPathOnRoad(nodeB.coordinate,IncludeEndpoints,Railroad,MarkPath,SmokePath) - - if gotpath then - return dist - else - return math.huge - end - - return nodeA.coordinate:Get3DDistance(nodeB.coordinate) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Misc functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ---- Find the closest node from a given coordinate. --- @param #ASTAR self --- @param Core.Point#COORDINATE Coordinate. --- @return #ASTAR.Node Cloest node to the coordinate. --- @return #number Distance to closest node in meters. -function ASTAR:FindClosestNode(Coordinate) - - local distMin=math.huge - local closeNode=nil - - for _,_node in pairs(self.nodes) do - local node=_node --#ASTAR.Node - - local dist=node.coordinate:Get2DDistance(Coordinate) - - if dist1000 then - self:I(self.lid.."Adding start node to node grid!") - self:AddNode(node) - end - - return self -end - ---- Add a node. --- @param #ASTAR self --- @param #ASTAR.Node Node The node to be added to the nodes table. --- @return #ASTAR self -function ASTAR:FindEndNode() - - local node, dist=self:FindClosestNode(self.endCoord) - - self.endNode=node - - if dist>1000 then - self:I(self.lid.."Adding end node to node grid!") - self:AddNode(node) - end - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Main A* pathfinding function -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- A* pathfinding function. This seaches the path along nodes between start and end nodes/coordinates. --- @param #ASTAR self --- @param #boolean ExcludeStartNode If *true*, do not include start node in found path. Default is to include it. --- @param #boolean ExcludeEndNode If *true*, do not include end node in found path. Default is to include it. --- @return #table Table of nodes from start to finish. -function ASTAR:GetPath(ExcludeStartNode, ExcludeEndNode) - - self:FindStartNode() - self:FindEndNode() - - local nodes=self.nodes - local start=self.startNode - local goal=self.endNode - - local closedset = {} - local openset = { start } - local came_from = {} - - local g_score, f_score = {}, {} - - g_score[start]=0 - f_score[start]=g_score[start]+self:_HeuristicCost(start, goal) - - -- Set start time. - local T0=timer.getAbsTime() - - -- Debug message. - local text=string.format("Starting A* pathfinding") - self:I(self.lid..text) - MESSAGE:New(text, 10, "ASTAR"):ToAllIf(self.Debug) - - local Tstart=UTILS.GetOSTime() - - while #openset > 0 do - - local current=self:_LowestFscore(openset, f_score) - - -- Check if we are at the end node. - if current.id==goal.id then - - local path=self:_UnwindPath({}, came_from, goal) - - if not ExcludeEndNode then - table.insert(path, goal) - end - - if ExcludeStartNode then - table.remove(path, 1) - end - - local Tstop=UTILS.GetOSTime() - - local dT=nil - if Tstart and Tstop then - dT=Tstop-Tstart - end - - -- Debug message. - local text=string.format("Found path with %d nodes (%d total nodes)", #path, #self.nodes) - if dT then - text=text..string.format(". OS Time %.6f seconds", dT) - end - text=text..string.format("\nNvalid = %d %d cached", self.nvalid, self.nvalidcache) - text=text..string.format("\nNcost = %d %d cached", self.ncost, self.ncostcache) - self:I(self.lid..text) - MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) - - return path - end - - self:_RemoveNode(openset, current) - table.insert(closedset, current) - - local neighbors=self:_NeighbourNodes(current, nodes) - - -- Loop over neighbours. - for _,neighbor in ipairs(neighbors) do - - if self:_NotIn(closedset, neighbor) then - - local tentative_g_score=g_score[current]+self:_DistNodes(current, neighbor) - - if self:_NotIn(openset, neighbor) or tentative_g_score < g_score[neighbor] then - - came_from[neighbor]=current - - g_score[neighbor]=tentative_g_score - f_score[neighbor]=g_score[neighbor]+self:_HeuristicCost(neighbor, goal) - - if self:_NotIn(openset, neighbor) then - table.insert(openset, neighbor) - end - - end - end - end - end - - -- Debug message. - local text=string.format("WARNING: Could NOT find valid path!") - self:E(self.lid..text) - MESSAGE:New(text, 60, "ASTAR"):ToAllIf(self.Debug) - - return nil -- no valid path -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- A* pathfinding helper functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Heuristic "cost" function to go from node A to node B. Default is the distance between the nodes. --- @param #ASTAR self --- @param #ASTAR.Node nodeA Node A. --- @param #ASTAR.Node nodeB Node B. --- @return #number "Cost" to go from node A to node B. -function ASTAR:_HeuristicCost(nodeA, nodeB) - - if self.ncost then - self.ncost=self.ncost+1 - else - self.ncost=1 - end - - -- Get chached cost if available. - local cost=nodeA.cost[nodeB.id] - if cost~=nil then - if self.ncostcache then - self.ncostcache=self.ncostcache+1 - else - self.ncostcache=1 - end - return cost - end - - local cost=nil - if self.CostFunc then - cost=self.CostFunc(nodeA, nodeB, unpack(self.CostArg)) - else - cost=self:_DistNodes(nodeA, nodeB) - end - - nodeA.cost[nodeB.id]=cost - nodeB.cost[nodeA.id]=cost -- Symmetric problem. - - return cost -end - ---- Check if going from a node to a neighbour is possible. --- @param #ASTAR self --- @param #ASTAR.Node node A node. --- @param #ASTAR.Node neighbor Neighbour node. --- @return #boolean If true, transition between nodes is possible. -function ASTAR:_IsValidNeighbour(node, neighbor) - - if self.nvalid then - self.nvalid=self.nvalid+1 - else - self.nvalid=1 - end - - local valid=node.valid[neighbor.id] - if valid~=nil then - --env.info(string.format("Node %d has valid=%s neighbour %d", node.id, tostring(valid), neighbor.id)) - if self.nvalidcache then - self.nvalidcache=self.nvalidcache+1 - else - self.nvalidcache=1 - end - return valid - end - - local valid=nil - if self.ValidNeighbourFunc then - valid=self.ValidNeighbourFunc(node, neighbor, unpack(self.ValidNeighbourArg)) - else - valid=true - end - - node.valid[neighbor.id]=valid - neighbor.valid[node.id]=valid -- Symmetric problem. - - return valid -end - ---- Calculate 2D distance between two nodes. --- @param #ASTAR self --- @param #ASTAR.Node nodeA Node A. --- @param #ASTAR.Node nodeB Node B. --- @return #number Distance between nodes in meters. -function ASTAR:_DistNodes(nodeA, nodeB) - return nodeA.coordinate:Get2DDistance(nodeB.coordinate) -end - ---- Function that calculates the lowest F score. --- @param #ASTAR self --- @param #table set The set of nodes. --- @param #number f_score F score. --- @return #ASTAR.Node Best node. -function ASTAR:_LowestFscore2(set, f_score) - - local lowest, bestNode = ASTAR.INF, nil - - for _, node in ipairs ( set ) do - - local score = f_score [ node ] - - if score < lowest then - lowest, bestNode = score, node - end - end - - return bestNode -end - ---- Function that calculates the lowest F score. --- @param #ASTAR self --- @param #table set The set of nodes. --- @param #number f_score F score. --- @return #ASTAR.Node Best node. -function ASTAR:_LowestFscore(set, f_score) - - local function sort(A, B) - local a=A --#ASTAR.Node - local b=B --#ASTAR.Node - return f_score[a] Date: Sat, 22 Aug 2020 00:09:36 +0200 Subject: [PATCH 45/79] Performance Optimizations --- Moose Development/Moose/Core/Base.lua | 3 + Moose Development/Moose/Core/Database.lua | 32 ++- Moose Development/Moose/Core/Point.lua | 15 +- Moose Development/Moose/Core/Zone.lua | 45 ++-- .../Moose/Functional/Warehouse.lua | 31 +-- Moose Development/Moose/Ops/OpsGroup.lua | 26 ++- .../Moose/Utilities/Profiler.lua | 16 +- Moose Development/Moose/Utilities/Utils.lua | 9 + Moose Development/Moose/Wrapper/Airbase.lua | 206 +++++++++++++----- .../Moose/Wrapper/Positionable.lua | 59 +++-- 10 files changed, 304 insertions(+), 138 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 827db5778..bdedec8a1 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -254,6 +254,8 @@ end -- @param #BASE Parent is the Parent class that the Child inherits from. -- @return #BASE Child function BASE:Inherit( Child, Parent ) + + -- Create child. local Child = routines.utils.deepCopy( Child ) if Child ~= nil then @@ -269,6 +271,7 @@ function BASE:Inherit( Child, Parent ) --Child:_SetDestructor() end + return Child end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 3b89132fc..6bd87bdd8 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -896,31 +896,25 @@ function DATABASE:_RegisterStatics() return self end ---- @param #DATABASE self +--- Register all world airbases. +-- @param #DATABASE self +-- @return #DATABASE self function DATABASE:_RegisterAirbases() - --[[ - local CoalitionsData = { AirbasesRed = coalition.getAirbases( coalition.side.RED ), AirbasesBlue = coalition.getAirbases( coalition.side.BLUE ), AirbasesNeutral = coalition.getAirbases( coalition.side.NEUTRAL ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - for DCSAirbaseId, DCSAirbase in pairs( CoalitionData ) do - - local DCSAirbaseName = DCSAirbase:getName() - - self:T( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } ) - self:AddAirbase( DCSAirbaseName ) - end - end - ]] - for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do - local DCSAirbaseName = DCSAirbase:getName() + + -- Get the airbase name. + local DCSAirbaseName = DCSAirbase:getName() - -- This gives the incorrect value to be inserted into the airdromeID for DCS 2.5.6! - local airbaseID=DCSAirbase:getID() + -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. + local airbaseID=DCSAirbase:getID() - local airbase=self:AddAirbase( DCSAirbaseName ) + -- Add and register airbase. + local airbase=self:AddAirbase( DCSAirbaseName ) - self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) + -- Debug output. + self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) + end return self diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index efa40a584..34ebf940f 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -228,6 +228,7 @@ do -- COORDINATE -- @return #COORDINATE function COORDINATE:New( x, y, z ) + --env.info("FF COORDINATE New") local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE self.x = x self.y = y @@ -717,12 +718,18 @@ do -- COORDINATE --- Return the 2D distance in meters between the target COORDINATE and the COORDINATE. -- @param #COORDINATE self - -- @param #COORDINATE TargetCoordinate The target COORDINATE. + -- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3. -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get2DDistance( TargetCoordinate ) - local TargetVec3 = TargetCoordinate:GetVec3() - local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + + local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} + + return UTILS.VecNorm(a) + + --local TargetVec3 = TargetCoordinate:GetVec3() + --local SourceVec3 = self:GetVec3() + + --return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 end --- Returns the temperature in Degrees Celsius. diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8ef630eee..b5b83d2a1 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -56,6 +56,7 @@ --- @type ZONE_BASE -- @field #string ZoneName Name of the zone. -- @field #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @field Core.Point#COORDINATE Coordinate object of the zone. -- @extends Core.Fsm#FSM @@ -221,22 +222,6 @@ function ZONE_BASE:GetPointVec2() end ---- Returns a @{Core.Point#COORDINATE} of the zone. --- @param #ZONE_BASE self --- @return Core.Point#COORDINATE The Coordinate of the zone. -function ZONE_BASE:GetCoordinate() - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local Coordinate = COORDINATE:NewFromVec2( Vec2 ) - - self:T2( { Coordinate } ) - - return Coordinate -end - - --- Returns the @{DCS#Vec3} of the zone. -- @param #ZONE_BASE self -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. @@ -276,15 +261,27 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate( Height ) --R2.1 - self:F2( self.ZoneName ) + self:F2(self.ZoneName) local Vec3 = self:GetVec3( Height ) - - local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) - - self:T2( { PointVec3 } ) - return PointVec3 + if self.Coordinate then + + -- Update coordinates. + self.Coordinate.x=Vec3.x + self.Coordinate.y=Vec3.y + self.Coordinate.z=Vec3.z + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + else + + -- Create a new coordinate object. + self.Coordinate=COORDINATE:NewFromVec3(Vec3) + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + end + + return self.Coordinate end @@ -433,12 +430,16 @@ ZONE_RADIUS = { -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius self.Vec2 = Vec2 + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + return self end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 82d09a386..fc78fefe7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -7521,13 +7521,6 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) for i,unit in pairs(units) do local coord=COORDINATE:New(unit.x, unit.alt, unit.y) coords[unit.name]=coord - --[[ - local airbase=coord:GetClosestAirbase() - local _,TermID, dist, spot=coord:GetClosestParkingSpot(airbase) - if dist<=10 then - env.info(string.format("Found client %s on parking spot %d at airbase %s", unit.name, TermID, airbase:GetName())) - end - ]] end end return coords @@ -7538,6 +7531,12 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- List of obstacles. local obstacles={} + + -- Check all clients. Clients dont change so we can put that out of the loop. + local clientcoords=_clients() + for clientname,_coord in pairs(clientcoords) do + table.insert(obstacles, {coord=_coord, size=15, name=clientname, type="client"}) + end -- Loop over all parking spots and get the currently present obstacles. -- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay! @@ -7553,22 +7552,16 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Check all units. for _,_unit in pairs(_units) do local unit=_unit --Wrapper.Unit#UNIT - local _coord=unit:GetCoordinate() + local _coord=unit:GetVec3() local _size=self:_GetObjectSize(unit:GetDCSObject()) local _name=unit:GetName() table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"}) end - -- Check all clients. - local clientcoords=_clients() - for clientname,_coord in pairs(clientcoords) do - table.insert(obstacles, {coord=_coord, size=15, name=clientname, type="client"}) - end - -- Check all statics. for _,static in pairs(_statics) do - local _vec3=static:getPoint() - local _coord=COORDINATE:NewFromVec3(_vec3) + local _coord=static:getPoint() + --local _coord=COORDINATE:NewFromVec3(_vec3) local _name=static:getName() local _size=self:_GetObjectSize(static) table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"}) @@ -7576,11 +7569,11 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Check all scenery. for _,scenery in pairs(_sceneries) do - local _vec3=scenery:getPoint() - local _coord=COORDINATE:NewFromVec3(_vec3) + local _coord=scenery:getPoint() + --local _coord=COORDINATE:NewFromVec3(_vec3) local _name=scenery:getTypeName() local _size=self:_GetObjectSize(scenery) - table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"}) + table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="scenery"}) end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 229daab70..bf36b6cc6 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -49,6 +49,7 @@ -- @field #boolean detectionOn If true, detected units of the group are analyzed. -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- +-- @field Core.Point#COORDINATE coordinate Current coordinate. -- @field Core.Point#COORDINATE position Position of the group at last status check. -- @field #number traveldist Distance traveled in meters. This is a lower bound! -- @field #number traveltime Time. @@ -481,15 +482,36 @@ function OPSGROUP:GetName() return self.groupname end +--- Get current 3D vector of the group. +-- @param #OPSGROUP self +-- @return DCS#Vec3 Vector with x,y,z components. +function OPSGROUP:GetVec3() + if self.group:IsAlive() then + self.group:GetVec3() + end + return nil +end + --- Get current coordinate of the group. -- @param #OPSGROUP self -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. function OPSGROUP:GetCoordinate() - if self:IsAlive()~=nil then - return self.group:GetCoordinate() + + local vec3=self:GetVec3() + + if vec3 then + + self.coordinate=self.coordinate or COORDINATE:New(0,0,0) + + self.coordinate.x=vec3.x + self.coordinate.y=vec3.y + self.coordinate.z=vec3.z + + return self.coordinate else self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") end + return nil end diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 76fee9dcc..78b4027b0 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -21,7 +21,8 @@ -- @field #number TstartGame Game start time timer.getTime(). -- @field #number TstartOS OS real start time os.clock. -- @field #boolean logUnknown Log unknown functions. Default is off. --- @field #number lowCpsThres Low calls per second threashold. Only write output if function has more calls per second than this value. +-- @field #number ThreshCPS Low calls per second threshold. Only write output if function has more calls per second than this value. +-- @field #number ThreshTtot Total time threshold. Only write output if total function CPU time is more than this value. -- @field #string fileNamePrefix Output file name prefix, e.g. "MooseProfiler". -- @field #string fileNameSuffix Output file name prefix, e.g. "txt" @@ -76,7 +77,7 @@ -- -- If you only want output for functions that are called more than X times per second, you can set -- --- PROFILER.lowCpsThres=1.5 +-- PROFILER.ThreshCPS=1.5 -- -- With this setting, only functions which are called more than 1.5 times per second are displayed. -- @@ -89,7 +90,8 @@ PROFILER = { fTimeTotal = {}, eventHandler = {}, logUnknown = false, - lowCpsThres = 0.0, + ThreshCPS = 0.0, + ThreshTtot = 0.005, fileNamePrefix = "MooseProfiler", fileNameSuffix = "txt" } @@ -147,7 +149,8 @@ function PROFILER.Start(Delay, Duration) else env.info(string.format("- Will be stopped when mission ends")) end - env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.lowCpsThres)) + env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS)) + env.info(string.format("- Total function time threshold %.3f/sec", PROFILER.ThreshTtot)) env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename())) env.info('###############################################################################') @@ -283,7 +286,10 @@ function PROFILER.showTable(data, f, runTimeGame) -- Calls per second. local cps=t.count/runTimeGame - if cps>=PROFILER.lowCpsThres then + local threshCPS=cps>=PROFILER.ThreshCPS + local threshTot=t.tm>=PROFILER.ThreshTtot + + if threshCPS and threshTot then -- Output local text=string.format("%30s: %8d calls %8.1f/sec - Time Total %8.3f sec (%.3f %%) %5.3f sec/call %s line %s", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 700e07475..bc7f32daa 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -191,21 +191,30 @@ end -- @param #table object The input table. -- @return #table Copy of the input table. UTILS.DeepCopy = function(object) + local lookup_table = {} + + -- Copy function. local function _copy(object) if type(object) ~= "table" then return object elseif lookup_table[object] then return lookup_table[object] end + local new_table = {} + lookup_table[object] = new_table + for index, value in pairs(object) do new_table[_copy(index)] = _copy(value) end + return setmetatable(new_table, getmetatable(object)) end + local objectreturn = _copy(object) + return objectreturn end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 234a92533..b246b621a 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -15,6 +15,15 @@ --- @type AIRBASE -- @field #string ClassName Name of the class, i.e. "AIRBASE". -- @field #table CategoryName Names of airbase categories. +-- @field #string AirbaseName Name of the airbase. +-- @field #number AirbaseID Airbase ID. +-- @field #number category Airbase category. +-- @field #table descriptors DCS descriptors. +-- @field #boolean isAirdrome Airbase is an airdrome. +-- @field #boolean isHelipad Airbase is a helipad. +-- @field #boolean isShip Airbase is a ship. +-- @field #table parking Parking spot data. +-- @field #table parkingByID Parking spot data table with ID as key. -- @field #number activerwyno Active runway number (forced). -- @extends Wrapper.Positionable#POSITIONABLE @@ -445,15 +454,44 @@ AIRBASE.TerminalType = { --- Create a new AIRBASE from DCSAirbase. -- @param #AIRBASE self -- @param #string AirbaseName The name of the airbase. --- @return Wrapper.Airbase#AIRBASE -function AIRBASE:Register( AirbaseName ) +-- @return #AIRBASE self +function AIRBASE:Register(AirbaseName) - local self = BASE:Inherit( self, POSITIONABLE:New( AirbaseName ) ) --#AIRBASE - self.AirbaseName = AirbaseName - self.AirbaseID = self:GetID(true) + -- Inherit everything from positionable. + local self=BASE:Inherit(self, POSITIONABLE:New(AirbaseName)) --#AIRBASE + + -- Set airbase name. + self.AirbaseName=AirbaseName + + -- Set airbase ID. + self.AirbaseID=self:GetID(true) + + -- Get descriptors. + self.descriptors=self:GetDesc() + + -- Category. + self.category=self.descriptors and self.descriptors.category or Airbase.Category.AIRDROME + + -- Set category. + if self.category==Airbase.Category.AIRDROME then + self.isAirdrome=true + elseif self.category==Airbase.Category.HELIPAD then + self.isHelipad=true + elseif self.category==Airbase.Category.SHIP then + self.isShip=true + else + self:E("ERROR: Unknown airbase category!") + end + + self:_InitParkingSpots() + local vec2=self:GetVec2() + + -- Init coordinate. + self:GetCoordinate() + if vec2 then - self.AirbaseZone = ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) + self.AirbaseZone=ZONE_RADIUS:New( AirbaseName, vec2, 2500 ) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end @@ -508,7 +546,9 @@ end -- @param #AIRBASE self -- @return DCS#Airbase DCS airbase object. function AIRBASE:GetDCSObject() - local DCSAirbase = Airbase.getByName( self.AirbaseName ) + + -- Get the DCS object. + local DCSAirbase = Airbase.getByName(self.AirbaseName) if DCSAirbase then return DCSAirbase @@ -533,7 +573,7 @@ function AIRBASE.GetAllAirbases(coalition, category) local airbases={} for _,_airbase in pairs(_DATABASE.AIRBASES) do local airbase=_airbase --#AIRBASE - if (coalition~=nil and airbase:GetCoalition()==coalition) or coalition==nil then + if coalition==nil or airbase:GetCoalition()==coalition then if category==nil or category==airbase:GetAirbaseCategory() then table.insert(airbases, airbase) end @@ -564,23 +604,7 @@ function AIRBASE:GetID(unique) local airbaseID=tonumber(DCSAirbase:getID()) local airbaseCategory=self:GetAirbaseCategory() - - --env.info(string.format("FF airbase=%s id=%s category=%s", tostring(AirbaseName), tostring(airbaseID), tostring(airbaseCategory))) - - -- No way AFIK to get the DCS version. So we check if the event exists. That should tell us if we are on DCS 2.5.6 or prior to that. - --[[ - if world.event.S_EVENT_KILL and world.event.S_EVENT_KILL>0 and airbaseCategory==Airbase.Category.AIRDROME then - - -- We have to take the key value of this loop! - airbaseID=DCSAirbaseId - - -- Now another quirk: for Caucasus, we need to add 11 to the key value to get the correct ID. See https://forums.eagle.ru/showpost.php?p=4210774&postcount=11 - if UTILS.GetDCSMap()==DCSMAP.Caucasus then - airbaseID=airbaseID+11 - end - end - ]] - + if AirbaseName==self.AirbaseName then if airbaseCategory==Airbase.Category.SHIP or airbaseCategory==Airbase.Category.HELIPAD then -- Ships get a negative sign as their unit number might be the same as the ID of another airbase. @@ -728,6 +752,69 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype) return spots end +--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. +-- @param #AIRBASE self +-- @return#AIRBASE self +function AIRBASE:_InitParkingSpots() + + -- Get parking data of all spots (free or occupied) + local parkingdata=self:GetParkingData(false) + + -- Init table. + self.parking={} + self.parkingByID={} + + self.NparkingTotal=0 + self.NparkingX=0 + self.NparkingY=0 + + -- Put coordinates of parking spots into table. + for _,spot in pairs(parkingdata) do + + -- New parking spot. + local park={} --#AIRBASE.ParkingSpot + park.Vec3=spot.vTerminalPos + park.Coordinate=COORDINATE:NewFromVec3(spot.vTerminalPos) + park.DistToRwy=spot.fDistToRW + park.Free=nil + park.TerminalID=spot.Term_Index + park.TerminalID0=spot.Term_Index_0 + 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 + + + self.parkingByID[park.TerminalID]=park + table.insert(self.parking, park) + end + + return self +end + +--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. +-- @param #AIRBASE self +-- @param #number TerminalID Terminal ID. +-- @return #AIRBASE.ParkingSpot Parking spot. +function AIRBASE:_GetParkingSpotByID(TerminalID) + return self.parkingByID[TerminalID] +end --- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. -- @param #AIRBASE self @@ -737,6 +824,7 @@ function AIRBASE:GetParkingSpotsTable(termtype) -- Get parking data of all spots (free or occupied) local parkingdata=self:GetParkingData(false) + -- Get parking data of all free spots. local parkingfree=self:GetParkingData(true) @@ -753,16 +841,19 @@ function AIRBASE:GetParkingSpotsTable(termtype) -- Put coordinates of parking spots into table. local spots={} for _,_spot in pairs(parkingdata) do + if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then - self:T2({_spot=_spot}) - local _free=_isfree(_spot) - local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) - table.insert(spots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) + + local spot=self:_GetParkingSpotByID(_spot.Term_Index) + + spot.Free=_isfree(_spot) -- updated + spot.TOAC=_spot.TO_AC -- updated + + table.insert(spots, spot) end + end - self:T2({ spots = spots } ) - return spots end @@ -781,8 +872,14 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) for _,_spot in pairs(parkingfree) do if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) and _spot.Term_Index>0 then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then - local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) - table.insert(freespots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) + + local spot=self:_GetParkingSpotByID(_spot.Term_Index) + + spot.Free=true -- updated + spot.TOAC=_spot.TO_AC -- updated + + table.insert(freespots, spot) + end end end @@ -795,14 +892,10 @@ end -- @param #number TerminalID The terminal ID of the parking spot. -- @return #AIRBASE.ParkingSpot Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". function AIRBASE:GetParkingSpotData(TerminalID) - self:F({TerminalID=TerminalID}) -- Get parking data. local parkingdata=self:GetParkingSpotsTable() - -- Debug output. - self:T2({parkingdata=parkingdata}) - for _,_spot in pairs(parkingdata) do local spot=_spot --#AIRBASE.ParkingSpot self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) @@ -1065,13 +1158,6 @@ function AIRBASE:CheckOnRunWay(group, radius, despawn) -- Get coordinates on runway. local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) - -- Mark runway spawn points. - --[[ - for _i,_coord in pairs(runwaypoints) do - _coord:MarkToAll(string.format("runway %d",_i)) - end - ]] - -- Get units of group. local units=group:GetUnits() @@ -1118,22 +1204,34 @@ function AIRBASE:CheckOnRunWay(group, radius, despawn) 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() - local desc=self:GetDesc() - local category=Airbase.Category.AIRDROME - - if desc and desc.category then - category=desc.category - else - self:E(string.format("ERROR: Cannot get category of airbase %s due to DCS 2.5.6 bug! Assuming it is an AIRDROME for now...", tostring(self.AirbaseName))) - end - return category + 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. diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index be85454a3..e7c5dcdf9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -15,6 +15,8 @@ -- @extends Wrapper.Identifiable#IDENTIFIABLE --- @type POSITIONABLE +-- @field Core.Point#COORDINATE coordinate Coordinate object. +-- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object. -- @extends Wrapper.Identifiable#IDENTIFIABLE @@ -45,6 +47,8 @@ POSITIONABLE = { ClassName = "POSITIONABLE", PositionableName = "", + coordinate = nil, + pointvec3 = nil, } --- @field #POSITIONABLE.__ @@ -268,17 +272,29 @@ end -- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec3() - self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then - local PositionableVec3 = self:GetPositionVec3() - - local PositionablePointVec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) - self:T2( PositionablePointVec3 ) - return PositionablePointVec3 + -- Get 3D vector. + local PositionableVec3 = self:GetPositionVec3() + + if self.pointvec3 then + --env.info("FF GetCoordinate GOT for "..tostring(self.PositionableName)) + + -- Update vector. + self.pointvec3.x=PositionableVec3.x + self.pointvec3.y=PositionableVec3.y + self.pointvec3.z=PositionableVec3.z + + else + --env.info("FF GetCoordinate NEW for "..tostring(self.PositionableName)) + + self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) + end + + return self.pointvec3 end BASE:E( { "Cannot GetPointVec3", Positionable = self, Alive = self:IsAlive() } ) @@ -290,21 +306,38 @@ end -- @param Wrapper.Positionable#POSITIONABLE self -- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. function POSITIONABLE:GetCoordinate() - self:F2( self.PositionableName ) + -- Get DCS object. local DCSPositionable = self:GetDCSObject() if DCSPositionable then + + -- Get the current position. local PositionableVec3 = self:GetPositionVec3() - local PositionableCoordinate = COORDINATE:NewFromVec3( PositionableVec3 ) - PositionableCoordinate:SetHeading( self:GetHeading() ) - PositionableCoordinate:SetVelocity( self:GetVelocityMPS() ) + if self.coordinate then + --env.info("FF GetCoordinate GOT for "..tostring(self.PositionableName)) + + -- Update vector. + self.coordinate.x=PositionableVec3.x + self.coordinate.y=PositionableVec3.y + self.coordinate.z=PositionableVec3.z + + else + --env.info("FF GetCoordinate NEW for "..tostring(self.PositionableName)) + + self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) + end + + + -- Set heading and velocity. + self.coordinate:SetHeading( self:GetHeading() ) + self.coordinate:SetVelocity( self:GetVelocityMPS() ) - self:T2( PositionableCoordinate ) - return PositionableCoordinate + return self.coordinate end + -- Error message. BASE:E( { "Cannot GetCoordinate", Positionable = self, Alive = self:IsAlive() } ) return nil @@ -1533,7 +1566,7 @@ end --- Returns true if the unit is within a @{Zone}. --- @param #STPOSITIONABLEATIC self +-- @param #POSITIONABLE self -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} function POSITIONABLE:IsInZone( Zone ) From f823fc6ee08f92fcb8227faf895008a30f99349d Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 Aug 2020 16:31:56 +0200 Subject: [PATCH 46/79] Update AI_Formation.lua Performance update --- Moose Development/Moose/AI/AI_Formation.lua | 500 +++++++++++--------- 1 file changed, 268 insertions(+), 232 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 2ecca0958..c1bfe5757 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -164,7 +164,7 @@ AI_FORMATION.__Enum.ReportType = { --- MENUPARAM type -- @type MENUPARAM -- @field #AI_FORMATION ParamSelf --- @field #Distance ParamDistance +-- @field #number ParamDistance -- @field #function ParamFunction -- @field #string ParamMessage @@ -207,9 +207,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -222,9 +222,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLine Trigger for AI_FORMATION @@ -232,9 +232,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLine Asynchronous Trigger for AI_FORMATION @@ -243,9 +243,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationTrail", "*" ) @@ -257,7 +257,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @return #boolean --- FormationTrail Handler OnAfter for AI_FORMATION @@ -268,14 +268,14 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. --- FormationTrail Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] FormationTrail -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. --- FormationTrail Asynchronous Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] __FormationTrail @@ -283,7 +283,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. self:AddTransition( "*", "FormationStack", "*" ) --- FormationStack Handler OnBefore for AI_FORMATION @@ -294,7 +294,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. -- @return #boolean @@ -306,7 +306,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- FormationStack Trigger for AI_FORMATION @@ -314,7 +314,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- FormationStack Asynchronous Trigger for AI_FORMATION @@ -323,7 +323,7 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. self:AddTransition( "*", "FormationLeftLine", "*" ) @@ -335,8 +335,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -348,16 +348,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftLine Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] FormationLeftLine -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftLine Asynchronous Trigger for AI_FORMATION @@ -365,8 +365,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationRightLine", "*" ) @@ -378,8 +378,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -391,16 +391,16 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightLine Trigger for AI_FORMATION -- @function [parent=#AI_FORMATION] FormationRightLine -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightLine Asynchronous Trigger for AI_FORMATION @@ -408,8 +408,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationLeftWing", "*" ) @@ -422,8 +422,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -436,8 +436,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftWing Trigger for AI_FORMATION @@ -445,8 +445,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationLeftWing Asynchronous Trigger for AI_FORMATION @@ -455,8 +455,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationRightWing", "*" ) @@ -469,8 +469,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -483,8 +483,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightWing Trigger for AI_FORMATION @@ -492,8 +492,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationRightWing Asynchronous Trigger for AI_FORMATION @@ -502,8 +502,8 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationCenterWing", "*" ) @@ -516,9 +516,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -531,9 +531,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationCenterWing Trigger for AI_FORMATION @@ -541,9 +541,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationCenterWing Asynchronous Trigger for AI_FORMATION @@ -552,9 +552,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationVic", "*" ) @@ -566,9 +566,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #boolean @@ -580,9 +580,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationVic Trigger for AI_FORMATION @@ -590,9 +590,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. --- FormationVic Asynchronous Trigger for AI_FORMATION @@ -601,9 +601,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. self:AddTransition( "*", "FormationBox", "*" ) @@ -615,9 +615,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. -- @return #boolean @@ -630,9 +630,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. @@ -641,9 +641,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #AI_FORMATION self -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. @@ -653,9 +653,9 @@ function AI_FORMATION:New( FollowUnit, FollowGroupSet, FollowName, FollowBriefin -- @param #number Delay -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. - -- @param #nubmer YStart The start position on the Y-axis in meters for the first group. + -- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. - -- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. + -- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. @@ -704,9 +704,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace, Formation ) --R2.1 @@ -751,7 +751,7 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationTrail( FollowGroupSet, From , Event , To, XStart, XSpace, YStart ) --R2.1 @@ -769,7 +769,7 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationStack( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace ) --R2.1 @@ -789,8 +789,8 @@ end -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationLeftLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 @@ -808,8 +808,8 @@ end -- @param #string Event -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationRightLine( FollowGroupSet, From , Event , To, XStart, YStart, ZStart, ZSpace ) --R2.1 @@ -828,8 +828,8 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationLeftWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 @@ -848,8 +848,8 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationRightWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, ZStart, ZSpace ) --R2.1 @@ -867,9 +867,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. function AI_FORMATION:onafterFormationCenterWing( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 @@ -905,9 +905,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @return #AI_FORMATION function AI_FORMATION:onafterFormationVic( FollowGroupSet, From , Event , To, XStart, XSpace, YStart, YSpace, ZStart, ZSpace ) --R2.1 @@ -924,9 +924,9 @@ end -- @param #string To -- @param #number XStart The start position on the X-axis in meters for the first group. -- @param #number XSpace The space between groups on the X-axis in meters for each sequent group. --- @param #nubmer YStart The start position on the Y-axis in meters for the first group. +-- @param #number YStart The start position on the Y-axis in meters for the first group. -- @param #number YSpace The space between groups on the Y-axis in meters for each sequent group. --- @param #nubmer ZStart The start position on the Z-axis in meters for the first group. +-- @param #number ZStart The start position on the Z-axis in meters for the first group. -- @param #number ZSpace The space between groups on the Z-axis in meters for each sequent group. -- @param #number ZLevels The amount of levels on the Z-axis. -- @return #AI_FORMATION @@ -1065,7 +1065,7 @@ end -- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. -- @param #string From From state. -- @param #string Event Event. --- @pram #string To The to state. +-- @param #string To The to state. function AI_FORMATION:onafterStop(FollowGroupSet, From, Event, To) --R2.1 self:E("Stopping formation.") end @@ -1075,7 +1075,7 @@ end -- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. -- @param #string From From state. -- @param #string Event Event. --- @pram #string To The to state. +-- @param #string To The to state. function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1 if From=="Stopped" then return false -- Deny transition. @@ -1083,7 +1083,12 @@ function AI_FORMATION:onbeforeFollow( FollowGroupSet, From, Event, To ) --R2.1 return true end ---- @param #AI_FORMATION self +--- Enter following state. +-- @param #AI_FORMATION self +-- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To The to state. function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 if self.FollowUnit:IsAlive() then @@ -1093,153 +1098,184 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 local CT1, CT2, CV1, CV2 CT1 = ClientUnit:GetState( self, "CT1" ) + local CuVec3=ClientUnit:GetVec3() + if CT1 == nil or CT1 == 0 then - ClientUnit:SetState( self, "CV1", ClientUnit:GetPointVec3() ) + ClientUnit:SetState( self, "CV1", CuVec3) ClientUnit:SetState( self, "CT1", timer.getTime() ) else CT1 = ClientUnit:GetState( self, "CT1" ) CT2 = timer.getTime() CV1 = ClientUnit:GetState( self, "CV1" ) - CV2 = ClientUnit:GetPointVec3() + CV2 = CuVec3 ClientUnit:SetState( self, "CT1", CT2 ) ClientUnit:SetState( self, "CV1", CV2 ) end - FollowGroupSet:ForEachGroupAlive( - --- @param Wrapper.Group#GROUP FollowGroup - -- @param Wrapper.Unit#UNIT ClientUnit - function( FollowGroup, Formation, ClientUnit, CT1, CV1, CT2, CV2 ) - - if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then - - self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) - - FollowGroup:OptionROTEvadeFire() - FollowGroup:OptionROEReturnFire() - - local GroupUnit = FollowGroup:GetUnit( 1 ) - local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) - if FollowFormation then - local FollowDistance = FollowFormation.x - - local GT1 = GroupUnit:GetState( self, "GT1" ) - - if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then - GroupUnit:SetState( self, "GV1", GroupUnit:GetPointVec3() ) - GroupUnit:SetState( self, "GT1", timer.getTime() ) - else - local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 - local CT = CT2 - CT1 - - local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 - - local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } - local Ca = math.atan2( CDv.x, CDv.z ) - - local GT1 = GroupUnit:GetState( self, "GT1" ) - local GT2 = timer.getTime() - local GV1 = GroupUnit:GetState( self, "GV1" ) - local GV2 = GroupUnit:GetPointVec3() - GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) - GroupUnit:SetState( self, "GT1", GT2 ) - GroupUnit:SetState( self, "GV1", GV2 ) - - - local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 - local GT = GT2 - GT1 - - - -- Calculate the distance - local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } - local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) - local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T - local Position = math.cos( Alpha_R ) - local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 - local Distance = GD * Position + - CS * 0.5 - - -- Calculate the group direction vector - local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } - - -- Calculate GH2, GH2 with the same height as CV2. - local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } - - -- Calculate the angle of GV to the orthonormal plane - local alpha = math.atan2( GV.x, GV.z ) - - local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) - local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) - - - -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. - -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) - local Inclination = ( Distance + FollowFormation.x ) / 10 - if Inclination < -30 then - Inclination = - 30 - end - local CVI = { x = CV2.x + CS * 10 * math.sin(Ca), - y = GH2.y + Inclination, -- + FollowFormation.y, - y = GH2.y, - z = CV2.z + CS * 10 * math.cos(Ca), - } - - -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. - local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } - - -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. - -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. - -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... - local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } - - -- Now we can calculate the group destination vector GDV. - local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } - - local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) - local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) - - local GDV_Formation = { - x = GDV.x - GVx, - y = GDV.y, - z = GDV.z - GVz - } - - if self.SmokeDirectionVector == true then - trigger.action.smoke( GDV, trigger.smokeColor.Green ) - trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) - end - - - - local Time = 120 - - local Speed = - ( Distance + FollowFormation.x ) / Time - - if Distance > -10000 then - Speed = - ( Distance + FollowFormation.x ) / 60 - end - - if Distance > -2500 then - Speed = - ( Distance + FollowFormation.x ) / 20 - end - - local GS = Speed + CS - - self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) - - - -- Now route the escort to the desired point with the desired speed. - FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) - end - end - end - end, - self, ClientUnit, CT1, CV1, CT2, CV2 - ) + --FollowGroupSet:ForEachGroupAlive( bla, self, ClientUnit, CT1, CV1, CT2, CV2) + + for _,_group in pairs(FollowGroupSet:GetSet()) do + local group=_group --Wrapper.Group#GROUP + if group and group:IsAlive() then + self:FollowMe(group, ClientUnit, CT1, CV1, CT2, CV2) + end + end self:__Follow( -self.dtFollow ) end end + +--- Follow me. +-- @param #AI_FORMATION self +-- @param Wrapper.Group#GROUP FollowGroup Follow group. +-- @param Wrapper.Unit#UNIT ClientUnit Client Unit. +-- @param DCS#Time CT1 Time +-- @param DCS#Vec3 CV1 Vec3 +-- @param DCS#Time CT2 Time +-- @param DCS#Vec3 CV2 Vec3 +function AI_FORMATION:FollowMe(FollowGroup, ClientUnit, CT1, CV1, CT2, CV2) + + if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then + + self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) + + FollowGroup:OptionROTEvadeFire() + FollowGroup:OptionROEReturnFire() + + local GroupUnit = FollowGroup:GetUnit( 1 ) + + local GuVec3=GroupUnit:GetVec3() + + local FollowFormation = FollowGroup:GetState( self, "FormationVec3" ) + + if FollowFormation then + local FollowDistance = FollowFormation.x + + local GT1 = GroupUnit:GetState( self, "GT1" ) + + if CT1 == nil or CT1 == 0 or GT1 == nil or GT1 == 0 then + GroupUnit:SetState( self, "GV1", GuVec3) + GroupUnit:SetState( self, "GT1", timer.getTime() ) + else + local CD = ( ( CV2.x - CV1.x )^2 + ( CV2.y - CV1.y )^2 + ( CV2.z - CV1.z )^2 ) ^ 0.5 + local CT = CT2 - CT1 + + local CS = ( 3600 / CT ) * ( CD / 1000 ) / 3.6 + + local CDv = { x = CV2.x - CV1.x, y = CV2.y - CV1.y, z = CV2.z - CV1.z } + local Ca = math.atan2( CDv.x, CDv.z ) + + local GT1 = GroupUnit:GetState( self, "GT1" ) + local GT2 = timer.getTime() + + local GV1 = GroupUnit:GetState( self, "GV1" ) + local GV2 = GuVec3 + + --[[ + GV2:AddX( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GV2:AddY( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + GV2:AddZ( math.random( -Formation.FlightRandomization / 2, Formation.FlightRandomization / 2 ) ) + ]] + + GV2.x=GV2.x+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + GV2.y=GV2.y+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + GV2.z=GV2.z+math.random( -self.FlightRandomization / 2, self.FlightRandomization / 2 ) + + + GroupUnit:SetState( self, "GT1", GT2 ) + GroupUnit:SetState( self, "GV1", GV2 ) + + + local GD = ( ( GV2.x - GV1.x )^2 + ( GV2.y - GV1.y )^2 + ( GV2.z - GV1.z )^2 ) ^ 0.5 + local GT = GT2 - GT1 + + + -- Calculate the distance + local GDv = { x = GV2.x - CV1.x, y = GV2.y - CV1.y, z = GV2.z - CV1.z } + local Alpha_T = math.atan2( GDv.x, GDv.z ) - math.atan2( CDv.x, CDv.z ) + local Alpha_R = ( Alpha_T < 0 ) and Alpha_T + 2 * math.pi or Alpha_T + local Position = math.cos( Alpha_R ) + local GD = ( ( GDv.x )^2 + ( GDv.z )^2 ) ^ 0.5 + local Distance = GD * Position + - CS * 0.5 + + -- Calculate the group direction vector + local GV = { x = GV2.x - CV2.x, y = GV2.y - CV2.y, z = GV2.z - CV2.z } + + -- Calculate GH2, GH2 with the same height as CV2. + local GH2 = { x = GV2.x, y = CV2.y + FollowFormation.y, z = GV2.z } + + -- Calculate the angle of GV to the orthonormal plane + local alpha = math.atan2( GV.x, GV.z ) + + local GVx = FollowFormation.z * math.cos( Ca ) + FollowFormation.x * math.sin( Ca ) + local GVz = FollowFormation.x * math.cos( Ca ) - FollowFormation.z * math.sin( Ca ) + + + -- Now we calculate the intersecting vector between the circle around CV2 with radius FollowDistance and GH2. + -- From the GeoGebra model: CVI = (x(CV2) + FollowDistance cos(alpha), y(GH2) + FollowDistance sin(alpha), z(CV2)) + local Inclination = ( Distance + FollowFormation.x ) / 10 + if Inclination < -30 then + Inclination = - 30 + end + + local CVI = { + x = CV2.x + CS * 10 * math.sin(Ca), + y = GH2.y + Inclination, -- + FollowFormation.y, + y = GH2.y, + z = CV2.z + CS * 10 * math.cos(Ca), + } + + -- Calculate the direction vector DV of the escort group. We use CVI as the base and CV2 as the direction. + local DV = { x = CV2.x - CVI.x, y = CV2.y - CVI.y, z = CV2.z - CVI.z } + + -- We now calculate the unary direction vector DVu, so that we can multiply DVu with the speed, which is expressed in meters / s. + -- We need to calculate this vector to predict the point the escort group needs to fly to according its speed. + -- The distance of the destination point should be far enough not to have the aircraft starting to swipe left to right... + local DVu = { x = DV.x / FollowDistance, y = DV.y, z = DV.z / FollowDistance } + + -- Now we can calculate the group destination vector GDV. + local GDV = { x = CVI.x, y = CVI.y, z = CVI.z } + + local ADDx = FollowFormation.x * math.cos(alpha) - FollowFormation.z * math.sin(alpha) + local ADDz = FollowFormation.z * math.cos(alpha) + FollowFormation.x * math.sin(alpha) + + local GDV_Formation = { + x = GDV.x - GVx, + y = GDV.y, + z = GDV.z - GVz + } + + -- Debug smoke. + if self.SmokeDirectionVector == true then + trigger.action.smoke( GDV, trigger.smokeColor.Green ) + trigger.action.smoke( GDV_Formation, trigger.smokeColor.White ) + end + + + + local Time = 120 + + local Speed = - ( Distance + FollowFormation.x ) / Time + + if Distance > -10000 then + Speed = - ( Distance + FollowFormation.x ) / 60 + end + + if Distance > -2500 then + Speed = - ( Distance + FollowFormation.x ) / 20 + end + + local GS = Speed + CS + + --self:F( { Distance = Distance, Speed = Speed, CS = CS, GS = GS } ) + + -- Now route the escort to the desired point with the desired speed. + FollowGroup:RouteToVec3( GDV_Formation, GS ) -- DCS models speed in Mps (Miles per second) + + end + end + end +end From f30c66424c69092b9763ad7c1ce2a05a9c33bc6c Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 22 Aug 2020 21:18:12 +0200 Subject: [PATCH 47/79] Radioqueue Profiler --- Moose Development/Moose/Core/RadioQueue.lua | 30 ++++++++++--------- .../Moose/Utilities/Profiler.lua | 13 ++++++-- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Core/RadioQueue.lua b/Moose Development/Moose/Core/RadioQueue.lua index 1ff08c747..6a217c815 100644 --- a/Moose Development/Moose/Core/RadioQueue.lua +++ b/Moose Development/Moose/Core/RadioQueue.lua @@ -17,7 +17,7 @@ -- -- @type RADIOQUEUE -- @field #string ClassName Name of the class "RADIOQUEUE". --- @field #boolean Debug Debug mode. More info. +-- @field #boolean Debugmode Debug mode. More info. -- @field #string lid ID for dcs.log. -- @field #number frequency The radio frequency in Hz. -- @field #number modulation The radio modulation. Either radio.modulation.AM or radio.modulation.FM. @@ -38,7 +38,7 @@ -- @extends Core.Base#BASE RADIOQUEUE = { ClassName = "RADIOQUEUE", - Debug = nil, + Debugmode = nil, lid = nil, frequency = nil, modulation = nil, @@ -55,7 +55,7 @@ RADIOQUEUE = { power = nil, numbers = {}, checking = nil, - schedonce = nil, + schedonce = false, } --- Radio queue transmission data. @@ -375,8 +375,10 @@ function RADIOQUEUE:Broadcast(transmission) sender:SetCommand(commandTransmit) -- Debug message. - local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") - MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug) + if self.Debugmode then + local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") + MESSAGE:New(text, 2, "RADIOQUEUE "..self.alias):ToAll() + end else @@ -388,10 +390,7 @@ function RADIOQUEUE:Broadcast(transmission) -- Try to get positon from sender unit/static. if self.sendername then - local coord=self:_GetRadioSenderCoord() - if coord then - vec3=coord:GetVec3() - end + vec3=self:_GetRadioSenderCoord() end -- Try to get fixed positon. @@ -408,8 +407,10 @@ function RADIOQUEUE:Broadcast(transmission) trigger.action.radioTransmission(filename, vec3, self.modulation, false, self.frequency, self.power) -- Debug message. - local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") - MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAllIf(self.Debug) + if self.Debugmode then + local text=string.format("file=%s, freq=%.2f MHz, duration=%.2f sec, subtitle=%s", filename, self.frequency/1000000, transmission.duration, transmission.subtitle or "") + MESSAGE:New(string.format(text, filename, transmission.duration, transmission.subtitle or ""), 5, "RADIOQUEUE "..self.alias):ToAll() + end end end @@ -532,6 +533,7 @@ function RADIOQUEUE:_GetRadioSender() -- Try the general default. if self.sendername then + -- First try to find a unit sender=UNIT:FindByName(self.sendername) @@ -547,7 +549,7 @@ end --- Get unit from which we want to transmit a radio message. This has to be an aircraft for subtitles to work. -- @param #RADIOQUEUE self --- @return Core.Point#COORDINATE Coordinate of the sender unit. +-- @return DCS#Vec3 Vector 3D. function RADIOQUEUE:_GetRadioSenderCoord() local vec3=nil @@ -560,7 +562,7 @@ function RADIOQUEUE:_GetRadioSenderCoord() -- Check that sender is alive and an aircraft. if sender and sender:IsAlive() then - return sender:GetCoordinate() + return sender:GetVec3() end -- Now try a static. @@ -568,7 +570,7 @@ function RADIOQUEUE:_GetRadioSenderCoord() -- Check that sender is alive and an aircraft. if sender then - return sender:GetCoordinate() + return sender:GetVec3() end end diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 78b4027b0..1c0bd73bc 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -75,11 +75,17 @@ -- -- It will tell you how many times a function was called in total, how many times per second, how much time in total and the percentage of time. -- --- If you only want output for functions that are called more than X times per second, you can set +-- If you only want output for functions that are called more than *X* times per second, you can set -- -- PROFILER.ThreshCPS=1.5 -- --- With this setting, only functions which are called more than 1.5 times per second are displayed. +-- With this setting, only functions which are called more than 1.5 times per second are displayed. The default setting is PROFILER.ThreshCPS=0.0 (no threshold). +-- +-- Furthermore, you can limit the output for functions that consumed a certain amount of CPU time in total by +-- +-- PROFILER.ThreshTtot=0.005 +-- +-- With this setting, which is also the default, only functions which in total used more than 5 milliseconds CPU time. -- -- @field #PROFILER PROFILER = { @@ -452,6 +458,9 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) PROFILER._flog(f,string.format("* Total functions = %d", #t)) PROFILER._flog(f,string.format("* Total func calls = %d", Calls)) PROFILER._flog(f,"") + PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS)) + PROFILER._flog(f,string.format("* Total func time threshold = %.3f/sec", PROFILER.ThreshTtot)) + PROFILER._flog(f,"") PROFILER._flog(f,"************************************************************************************************************************") PROFILER._flog(f,"") PROFILER.showTable(t, f, runTimeGame) From cfc45cf0682274eeb758d5073c13b4711b40fcc0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 23 Aug 2020 01:08:02 +0200 Subject: [PATCH 48/79] Warehouse marker and spawning update --- .../Moose/Functional/Warehouse.lua | 75 ++++++++++-------- .../Moose/Wrapper/Positionable.lua | 76 ++++++++++++++----- 2 files changed, 99 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index fc78fefe7..767789001 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -58,6 +58,10 @@ -- @field Core.Point#COORDINATE rail Closest point to warehouse on rail. -- @field Core.Zone#ZONE spawnzone Zone in which assets are spawned. -- @field #number uid Unique ID of the warehouse. +-- @field #boolean markerOn If true, markers are displayed on the F10 map. +-- @field Wrapper.Marker#MARKER markerWarehouse Marker warehouse. +-- @field Wrapper.Marker#MARKER markerRoad Road connection. +-- @field Wrapper.Marker#MARKER markerRail Rail road connection. -- @field #number markerid ID of the warehouse marker at the airbase. -- @field #number dTstatus Time interval in seconds of updating the warehouse status and processing new events. Default 30 seconds. -- @field #number queueid Unit id of each request in the queue. Essentially a running number starting at one and incremented when a new request is added. @@ -80,7 +84,6 @@ -- @field #number lowfuelthresh Low fuel threshold. Triggers the event AssetLowFuel if for any unit fuel goes below this number. -- @field #boolean respawnafterdestroyed If true, warehouse is respawned after it was destroyed. Assets are kept. -- @field #number respawndelay Delay before respawn in seconds. --- @field #boolean markerOn If true, markers are displayed on the F10 map. -- @extends Core.Fsm#FSM --- Have your assets at the right place at the right time - or not! @@ -1560,7 +1563,6 @@ WAREHOUSE = { rail = nil, spawnzone = nil, uid = nil, - markerid = nil, dTstatus = 30, queueid = 0, stock = {}, @@ -3372,7 +3374,7 @@ function WAREHOUSE:onafterStop(From, Event, To) self:_UpdateWarehouseMarkText() -- Clear all pending schedules. - --self.CallScheduler:Clear() + self.CallScheduler:Clear() end --- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. @@ -7497,7 +7499,7 @@ end function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Init default - local scanradius=100 + local scanradius=25 local scanunits=true local scanstatics=true local scanscenery=false @@ -7527,14 +7529,14 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) end -- Get parking spot data table. This contains all free and "non-free" spots. - local parkingdata=airbase:GetParkingSpotsTable() + local parkingdata=airbase.parking --airbase:GetParkingSpotsTable() -- List of obstacles. local obstacles={} -- Check all clients. Clients dont change so we can put that out of the loop. - local clientcoords=_clients() - for clientname,_coord in pairs(clientcoords) do + self.clientcoords=self.clientcoords or _clients() + for clientname,_coord in pairs(self.clientcoords) do table.insert(obstacles, {coord=_coord, size=15, name=clientname, type="client"}) end @@ -7605,20 +7607,9 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID - local _toac=parkingspot.TOAC - - --env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles)) - local free=true local problem=nil - -- Safe parking using TO_AC from DCS result. - self:T2(self.lid..string.format("Parking spot %d TOAC=%s (safe park=%s)", _termid, tostring(_toac), tostring(self.safeparking))) - if self.safeparking and _toac then - free=false - self:T(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC)", _termid)) - end - -- Loop over all obstacles. for _,obstacle in pairs(obstacles) do @@ -8405,27 +8396,47 @@ function WAREHOUSE:_UpdateWarehouseMarkText() if self.markerOn then - -- Create a mark with the current assets in stock. - if self.markerid~=nil then - trigger.action.removeMark(self.markerid) - end - - -- Get assets in stock. - local _data=self:GetStockInfo(self.stock) - - -- Text. + -- Marker text. local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n", self:GetState(), #self.stock) - - for _attribute,_count in pairs(_data) do + for _attribute,_count in pairs(self:GetStockInfo(self.stock) or {}) do if _count>0 then local attribute=tostring(UTILS.Split(_attribute, "_")[2]) text=text..string.format("%s=%d, ", attribute,_count) end end + + local coordinate=self:GetCoordinate() + local coalition=self:GetCoalition() - -- Create/update marker at warehouse in F10 map. - self.markerid=self:GetCoordinate():MarkToCoalition(text, self:GetCoalition(), true) - + if not self.markerWarehouse then + + -- Create a new marker. + self.markerWarehouse=MARKER:New(coordinate, text):ToCoalition(coalition) + + else + + local refresh=false + + if self.markerWarehouse.text~=text then + self.markerWarehouse.text=text + refresh=true + end + + if self.markerWarehouse.coordinate~=coordinate then + self.markerWarehouse.coordinate=coordinate + refresh=true + end + + if self.markerWarehouse.coalition~=coalition then + self.markerWarehouse.coalition=coalition + refresh=true + end + + if refresh then + self.markerWarehouse:Refresh() + end + + end end end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index e7c5dcdf9..76b35ec64 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -219,6 +219,26 @@ function POSITIONABLE:GetPositionVec3() return nil end +--- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetVec3() + self:F2( self.PositionableName ) + + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + local PositionableVec3 = DCSPositionable:getPosition().p + self:T3( PositionableVec3 ) + return PositionableVec3 + end + + BASE:E( { "Cannot GetVec3", Positionable = self, Alive = self:IsAlive() } ) + + return nil +end + --- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Vec2 The 2D point vector of the POSITIONABLE. @@ -302,6 +322,42 @@ function POSITIONABLE:GetPointVec3() return nil end +--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. +-- @param Wrapper.Positionable#POSITIONABLE self +-- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. +function POSITIONABLE:GetCoord() + + -- Get DCS object. + local DCSPositionable = self:GetDCSObject() + + if DCSPositionable then + + -- Get the current position. + local PositionableVec3 = self:GetPositionVec3() + + if self.coordinate then + --env.info("FF GetCoordinate GOT for "..tostring(self.PositionableName)) + + -- Update vector. + self.coordinate.x=PositionableVec3.x + self.coordinate.y=PositionableVec3.y + self.coordinate.z=PositionableVec3.z + + else + --env.info("FF GetCoordinate NEW for "..tostring(self.PositionableName)) + + self.coordinate=COORDINATE:NewFromVec3(PositionableVec3) + end + + return self.coordinate + end + + -- Error message. + BASE:E( { "Cannot GetCoordinate", Positionable = self, Alive = self:IsAlive() } ) + + return nil +end + --- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param Wrapper.Positionable#POSITIONABLE self -- @return Core.Point#COORDINATE The COORDINATE of the POSITIONABLE. @@ -417,26 +473,6 @@ function POSITIONABLE:GetRandomVec3( Radius ) return nil end ---- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. --- @param Wrapper.Positionable#POSITIONABLE self --- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. --- @return #nil The POSITIONABLE is not existing or alive. -function POSITIONABLE:GetVec3() - self:F2( self.PositionableName ) - - local DCSPositionable = self:GetDCSObject() - - if DCSPositionable then - local PositionableVec3 = DCSPositionable:getPosition().p - self:T3( PositionableVec3 ) - return PositionableVec3 - end - - BASE:E( { "Cannot GetVec3", Positionable = self, Alive = self:IsAlive() } ) - - return nil -end - --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self From 3f39ec0ae00d39a9fcd207ebe25d2b1425d9dc9c Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 Aug 2020 01:03:18 +0200 Subject: [PATCH 49/79] Stuff --- Moose Development/Moose/Core/Fsm.lua | 9 +- .../Moose/Functional/Warehouse.lua | 8 + Moose Development/Moose/Ops/ATIS.lua | 19 +-- Moose Development/Moose/Ops/Auftrag.lua | 2 +- .../Moose/Utilities/Profiler.lua | 59 +++++-- Moose Development/Moose/Wrapper/Airbase.lua | 149 ++++++++++++------ 6 files changed, 169 insertions(+), 77 deletions(-) diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index a9725c636..70275d945 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -642,10 +642,11 @@ do -- FSM return errmsg end - -- Protected call. - local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + --return self[handler](self, unpack( params )) - return Value + -- Protected call. + local Result, Value = xpcall( function() return self[handler]( self, unpack( params ) ) end, ErrorHandler ) + return Value end end @@ -773,7 +774,7 @@ do -- FSM end else - self:I( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) + self:T( "*** FSM *** NO Transition *** " .. self.current .. " --> " .. EventName .. " --> ? " ) end return nil diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 767789001..eb851a28e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -7530,6 +7530,10 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Get parking spot data table. This contains all free and "non-free" spots. local parkingdata=airbase.parking --airbase:GetParkingSpotsTable() + + --- + -- Find all obstacles + --- -- List of obstacles. local obstacles={} @@ -7579,6 +7583,10 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) end end + + --- + -- Get Parking Spots + --- -- Parking data for all assets. local parking={} diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 15e37ed47..29b2918b2 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -513,7 +513,7 @@ ATIS.Sound = { MegaHertz={filename="MegaHertz.ogg", duration=0.87}, Meters={filename="Meters.ogg", duration=0.59}, MetersPerSecond={filename="MetersPerSecond.ogg", duration=1.14}, - Miles={filename="Miles.ogg", duration=1.04}, + Miles={filename="Miles.ogg", duration=0.60}, MillimetersOfMercury={filename="MillimetersOfMercury.ogg", duration=1.53}, Minus={filename="Minus.ogg", duration=0.64}, N0={filename="N-0.ogg", duration=0.55}, @@ -534,6 +534,7 @@ ATIS.Sound = { Right={filename="Right.ogg", duration=0.44}, Snow={filename="Snow.ogg", duration=0.48}, SnowStorm={filename="SnowStorm.ogg", duration=0.82}, + StatuteMiles={filename="StatuteMiles.ogg", duration=1.15}, SunriseAt={filename="SunriseAt.ogg", duration=0.92}, SunsetAt={filename="SunsetAt.ogg", duration=0.95}, Temperature={filename="Temperature.ogg", duration=0.64}, @@ -553,6 +554,7 @@ ATIS.Sound = { TACANChannel={filename="TACANChannel.ogg", duration=0.88}, PRMGChannel={filename="PRMGChannel.ogg", duration=1.18}, RSBNChannel={filename="RSBNChannel.ogg", duration=1.14}, + Zulu={filename="Zulu.ogg", duration=0.62}, } @@ -925,7 +927,7 @@ function ATIS:SetAltimeterQNH(switch) return self end --- Suppresses QFE readout. Default is to report both QNH and QFE. +--- Suppresses QFE readout. Default is to report both QNH and QFE. -- @param #ATIS self -- @return #ATIS self function ATIS:ReportQNHOnly() @@ -995,7 +997,7 @@ function ATIS:SetZuluTimeDifference(delta) return self end --- Suppresses local time, sunrise, and sunset. Default is to report all these times. +--- Suppresses local time, sunrise, and sunset. Default is to report all these times. -- @param #ATIS self -- @return #ATIS self function ATIS:ReportZuluTimeOnly() @@ -1502,7 +1504,7 @@ function ATIS:onafterBroadcast(From, Event, To) -- Zulu Time subtitle=string.format("%s Zulu", ZULU) self.radioqueue:Number2Transmission(ZULU, nil, 0.5) - self:Transmission(ATIS.Sound.TimeZulu, 0.2, subtitle) + self:Transmission(ATIS.Sound.Zulu, 0.2, subtitle) alltext=alltext..";\n"..subtitle if not self.zulutimeonly then @@ -1557,7 +1559,7 @@ function ATIS:onafterBroadcast(From, Event, To) if self.metric then self:Transmission(ATIS.Sound.Kilometers, 0.2) else - self:Transmission(ATIS.Sound.Miles, 0.2) + self:Transmission(ATIS.Sound.StatuteMiles, 0.2) end alltext=alltext..";\n"..subtitle @@ -1974,13 +1976,6 @@ function ATIS:onafterBroadcast(From, Event, To) alltext=alltext..";\n"..subtitle end - - --[[ - -- End of Information Alpha, Bravo, ... - subtitle=string.format("End of information %s", NATO) - self:Transmission(ATIS.Sound.EndOfInformation, 0.5, subtitle) - self.radioqueue:NewTransmission(string.format("NATO Alphabet/%s.ogg", NATO), 0.75, self.soundpath) - --]] -- Advice on initial... subtitle=string.format("Advise on initial contact, you have information %s", NATO) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index e3345c937..16edd0aef 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1586,7 +1586,7 @@ end --- Set Reaction on Threat (ROT) for this mission. -- @param #AUFTRAG self --- @param #string roe Mission ROT. +-- @param #string rot Mission ROT. -- @return #AUFTRAG self function AUFTRAG:SetROT(rot) diff --git a/Moose Development/Moose/Utilities/Profiler.lua b/Moose Development/Moose/Utilities/Profiler.lua index 1c0bd73bc..a4bce95b5 100644 --- a/Moose Development/Moose/Utilities/Profiler.lua +++ b/Moose Development/Moose/Utilities/Profiler.lua @@ -156,8 +156,9 @@ function PROFILER.Start(Delay, Duration) env.info(string.format("- Will be stopped when mission ends")) end env.info(string.format("- Calls per second threshold %.3f/sec", PROFILER.ThreshCPS)) - env.info(string.format("- Total function time threshold %.3f/sec", PROFILER.ThreshTtot)) - env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename())) + env.info(string.format("- Total function time threshold %.3f sec", PROFILER.ThreshTtot)) + env.info(string.format("- Output file \"%s\" in your DCS log file folder", PROFILER.getfilename(PROFILER.fileNameSuffix))) + env.info(string.format("- Output file \"%s\" in CSV format", PROFILER.getfilename("csv"))) env.info('###############################################################################') @@ -306,13 +307,47 @@ function PROFILER.showTable(data, f, runTimeGame) end +--- Print csv file. +-- @param #table data Data table. +-- @param #number runTimeGame Game run time in seconds. +function PROFILER.printCSV(data, runTimeGame) + + -- Output file. + local file=PROFILER.getfilename("csv") + local g=io.open(file, 'w') + + -- Header. + local text="Function,Total Calls,Calls per Sec,Total Time,Total in %,Sec per Call,Source File;Line Number," + g:write(text.."\r\n") + + -- Loop over data. + for i=1, #data do + local t=data[i] --#PROFILER.Data + + -- Calls per second. + local cps=t.count/runTimeGame + + -- Output + local txt=string.format("%s,%d,%.1f,%.3f,%.3f,%.3f,%s,%s,", t.func, t.count, cps, t.tm, t.tm/runTimeGame*100, t.tm/t.count, tostring(t.src), tostring(t.line)) + g:write(txt.."\r\n") + + end + + -- Close file. + g:close() +end + + --- Write info to output file. +-- @param #string ext Extension. -- @return #string File name. -function PROFILER.getfilename() +function PROFILER.getfilename(ext) local dir=lfs.writedir()..[[Logs\]] - local file=dir..PROFILER.fileNamePrefix.."."..PROFILER.fileNameSuffix + ext=ext or PROFILER.fileNameSuffix + + local file=dir..PROFILER.fileNamePrefix.."."..ext if not UTILS.FileExists(file) then return file @@ -320,7 +355,7 @@ function PROFILER.getfilename() for i=1,999 do - local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, PROFILER.fileNameSuffix) + local file=string.format("%s%s-%03d.%s", dir,PROFILER.fileNamePrefix, i, ext) if not UTILS.FileExists(file) then return file @@ -336,7 +371,7 @@ end function PROFILER.showInfo(runTimeGame, runTimeOS) -- Output file. - local file=PROFILER.getfilename() + local file=PROFILER.getfilename(PROFILER.fileNameSuffix) local f=io.open(file, 'w') -- Gather data. @@ -427,16 +462,15 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) table.insert(t, tpairs) end - env.info("**************************************************************************************************") - env.info(string.format("Profiler")) - env.info(string.format("--------")) + env.info('############################ Profiler Stopped ############################') env.info(string.format("* Runtime Game : %s = %d sec", UTILS.SecondsToClock(runTimeGame, true), runTimeGame)) env.info(string.format("* Runtime Real : %s = %d sec", UTILS.SecondsToClock(runTimeOS, true), runTimeOS)) env.info(string.format("* Function time : %s = %.1f sec (%.1f percent of runtime game)", UTILS.SecondsToClock(Ttot, true), Ttot, Ttot/runTimeGame*100)) env.info(string.format("* Total functions : %d", #t)) env.info(string.format("* Total func calls : %d", Calls)) env.info(string.format("* Writing to file : \"%s\"", file)) - env.info("**************************************************************************************************") + env.info(string.format("* Writing to file : \"%s\"", PROFILER.getfilename("csv"))) + env.info("##############################################################################") -- Sort by total time. table.sort(t, function(a,b) return a.tm>b.tm end) @@ -459,7 +493,7 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) PROFILER._flog(f,string.format("* Total func calls = %d", Calls)) PROFILER._flog(f,"") PROFILER._flog(f,string.format("* Calls per second threshold = %.3f/sec", PROFILER.ThreshCPS)) - PROFILER._flog(f,string.format("* Total func time threshold = %.3f/sec", PROFILER.ThreshTtot)) + PROFILER._flog(f,string.format("* Total func time threshold = %.3f sec", PROFILER.ThreshTtot)) PROFILER._flog(f,"") PROFILER._flog(f,"************************************************************************************************************************") PROFILER._flog(f,"") @@ -498,5 +532,8 @@ function PROFILER.showInfo(runTimeGame, runTimeOS) PROFILER._flog(f,"************************************************************************************************************************") -- Close file. f:close() + + -- Print csv file. + PROFILER.printCSV(t, runTimeGame) end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index b246b621a..4de641e41 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1292,41 +1292,124 @@ function AIRBASE:GetRunwayData(magvar, mark) return {} end - -- Get spawn points on runway. + -- Get spawn points on runway. These can be used to determine the runway heading. local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) + + -- Debug: For finding the numbers of the spawn points belonging to each runway. + if false then + for i,_coord in pairs(runwaycoords) do + local coord=_coord --Core.Point#COORDINATE + coord:Translate(100, 0):MarkToAll("Runway i="..i) + end + end -- Magnetic declination. magvar=magvar or UTILS.GetMagneticDeclination() + -- Number of runways. local N=#runwaycoords - local dN=2 - local ex=false + local N2=N/2 + local exception=false + -- Airbase name. local name=self:GetName() + + + -- Exceptions if name==AIRBASE.Nevada.Jean_Airport or name==AIRBASE.Nevada.Creech_AFB or name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or - name==AIRBASE.PersianGulf.Kish_International_Airport then + name==AIRBASE.PersianGulf.Kish_International_Airport + then - N=#runwaycoords/2 - dN=1 - ex=true + -- 1-->4, 2-->3, 3-->2, 4-->1 + exception=1 + + elseif UTILS.GetDCSMap()==DCSMAP.Syria and N>=2 and + name~=AIRBASE.Syria.Minakh and + name~=AIRBASE.Syria.Damascus and + name~=AIRBASE.Syria.Khalkhalah and + name~=AIRBASE.Syria.Marj_Ruhayyil and + name~=AIRBASE.Syria.Beirut_Rafic_Hariri then + + -- 1-->3, 2-->4, 3-->1, 4-->2 + exception=2 + + end + + local function f(i) + + local j + + if exception==1 then + + j=N-(i+1) -- 1-->4, 2-->3 + + elseif exception==2 then + + if i<=N2 then + j=i+N2 -- 1-->3, 2-->4 + else + j=i-N2 -- 3-->1, 4-->3 + end + + else + + if i%2==0 then + j=i-1 -- even 2-->1, 4-->3 + else + j=i+1 -- odd 1-->2, 3-->4 + end + + end + + -- Special case where there is no obvious order. + if name==AIRBASE.Syria.Beirut_Rafic_Hariri then + if i==1 then + j=3 + elseif i==2 then + j=6 + elseif i==3 then + j=1 + elseif i==4 then + j=5 + elseif i==5 then + j=4 + elseif i==6 then + j=2 + end + end + + if name==AIRBASE.Syria.Ramat_David then + if i==1 then + j=4 + elseif i==2 then + j=6 + elseif i==3 then + j=5 + elseif i==4 then + j=1 + elseif i==5 then + j=3 + elseif i==6 then + j=2 + end + end + + return j end - for i=1,N,dN do + for i=1,N do - local j=i+1 - if ex then - --j=N+i - j=#runwaycoords-i+1 - end + -- Get the other spawn point coordinate. + local j=f(i) -- Coordinates of the two runway points. - local c1=runwaycoords[i] --Core.Point#COORDINATES - local c2=runwaycoords[j] --Core.Point#COORDINATES + local c1=runwaycoords[i] --Core.Point#COORDINATE + local c2=runwaycoords[j] --Core.Point#COORDINATE -- Heading of runway. local hdg=c1:HeadingTo(c2) @@ -1343,11 +1426,11 @@ function AIRBASE:GetRunwayData(magvar, mark) runway.endpoint=c2 -- Debug info. - self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length)) + self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) -- Debug mark if mark then - runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m", runway.idx, runway.heading, magvar, runway.length)) + runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m, i=%d, j=%d", runway.idx, runway.heading, magvar, runway.length, i, j)) end -- Add runway. @@ -1355,38 +1438,6 @@ function AIRBASE:GetRunwayData(magvar, mark) end - -- Get inverse runways - local inverse={} - for _,_runway in pairs(runways) do - local r=_runway --#AIRBASE.Runway - - local runway={} --#AIRBASE.Runway - runway.heading=r.heading-180 - if runway.heading<0 then - runway.heading=runway.heading+360 - end - runway.idx=string.format("%02d", math.max(0, UTILS.Round((runway.heading-magvar)/10, 0))) - runway.length=r.length - runway.position=r.endpoint - runway.endpoint=r.position - - -- Debug info. - self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length)) - - -- Debug mark - if mark then - runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m", runway.idx, runway.heading, magvar, runway.length)) - end - - -- Add runway. - table.insert(inverse, runway) - end - - -- Add inverse runway. - for _,runway in pairs(inverse) do - table.insert(runways, runway) - end - return runways end From 06600b2a94a00e52b8d79901c1dffc22efa7a46a Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 25 Aug 2020 17:55:53 +0200 Subject: [PATCH 50/79] Ops --- Moose Development/Moose/Ops/AirWing.lua | 16 +- Moose Development/Moose/Ops/ChiefOfStaff.lua | 4 +- Moose Development/Moose/Ops/Intelligence.lua | 134 ++++++---- Moose Development/Moose/Wrapper/Airbase.lua | 242 ++++++++++--------- 4 files changed, 227 insertions(+), 169 deletions(-) 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 From f9ec21a2b55efb6cd78df407997475e1426d95d3 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 26 Aug 2020 00:01:52 +0200 Subject: [PATCH 51/79] Ops --- Moose Development/Moose/Ops/AirWing.lua | 3 ++ Moose Development/Moose/Ops/ChiefOfStaff.lua | 22 +++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 39 +++++++++----------- Moose Development/Moose/Ops/Intelligence.lua | 4 +- Moose Development/Moose/Ops/Target.lua | 2 +- Moose Development/Moose/Wrapper/Group.lua | 17 ++++----- 6 files changed, 48 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 8b7872cbf..3169eb995 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -1571,6 +1571,9 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) -- Create a flight group. local flightgroup=self:_CreateFlightGroup(asset) + -- Set home base. + flightgroup.homebase=self.airbase + --- -- Asset --- diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua index 6c24eb4d5..6655ff463 100644 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ b/Moose Development/Moose/Ops/ChiefOfStaff.lua @@ -388,9 +388,7 @@ function CHIEF:onafterStatus(From, Event, To) end -- Create missions for all new contacts. - local Nred=0 - local Nyellow=0 - local Nengage=0 + local Nred=0 ; local Nyellow=0 ; local Nengage=0 for _,_contact in pairs(self.Contacts) do local contact=_contact --#CHIEF.Contact local group=contact.group --Wrapper.Group#GROUP @@ -438,7 +436,10 @@ function CHIEF:onafterStatus(From, Event, To) end - -- Set defcon. + --- + -- Defcon + --- + -- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones. if Nred>0 then self:SetDefcon(CHIEF.DEFCON.RED) @@ -447,15 +448,22 @@ function CHIEF:onafterStatus(From, Event, To) else self:SetDefcon(CHIEF.DEFCON.GREEN) end - - + + --- + -- Mission Queue + --- + -- Check mission queue and assign one PLANNED mission. self:CheckMissionQueue() local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) self:I(self.lid..text) + + --- + -- Contacts + --- - -- Infor about contacts. + -- Info about contacts. if #self.Contacts>0 then local text="Contacts:" for i,_contact in pairs(self.Contacts) do diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 1ae0ec45e..87e1e3ebe 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -728,9 +728,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Short info. if self.verbose>0 then - local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Current=%d. Missions=%s. Waypoint=%d/%d. Detected=%d. Destination=%s, FC=%s", + local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Curr=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s", fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, - self.detectedunits:Count(), self.destbase and self.destbase:GetName() or "unknown", self.flightcontrol and self.flightcontrol.airbasename or "none") + self.detectedunits:Count(), self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "unknown") self:I(self.lid..text) end @@ -890,6 +890,8 @@ end -- @param Core.Event#EVENTDATA EventData Event data. function FLIGHTGROUP:OnEventBirth(EventData) + env.info(string.format("EVENT: Birth for unit %s", tostring(EventData.IniUnitName))) + -- Check that this is the right group. if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then local unit=EventData.IniUnit @@ -899,11 +901,6 @@ function FLIGHTGROUP:OnEventBirth(EventData) -- Set group. self.group=self.group or EventData.IniGroup - if not self.groupinitialized then - --TODO: actually that is not very good here as if the first unit is born and in initgroup we initialize all elements! - self:_InitGroup() - end - if self.respawning then local function reset() @@ -934,7 +931,7 @@ function FLIGHTGROUP:OnEventBirth(EventData) end -- Set element to spawned state. - self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) + self:I(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) self:ElementSpawned(element) end @@ -1365,7 +1362,12 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterSpawned(From, Event, To) - self:T(self.lid..string.format("Flight spawned")) + self:I(self.lid..string.format("Flight spawned")) + + -- TODO: general routine in opsgroup + self.traveldist=0 + self.traveltime=timer.getAbsTime() + self.position=self:GetCoordinate() if self.ai then @@ -1776,14 +1778,17 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Number of remaining tasks/missions? if nTasks==0 and nMissions==0 then + + local destbase=self.destbase or self.homebase + local destzone=self.destzone or self.homezone -- Send flight to destination. - if self.destbase then + if destbase then self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") - self:__RTB(-3, self.destbase) - elseif self.destzone then + self:__RTB(-3, destbase) + elseif destzone then self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!") - self:__RTZ(-3, self.destzone) + self:__RTZ(-3, destzone) else self:I(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") self:__Wait(-1) @@ -2501,14 +2506,6 @@ function FLIGHTGROUP:_InitGroup() -- Group ammo. self.ammo=self:GetAmmoTot() - -- Initial fuel mass. - -- TODO: this is a unit property! - self.fuelmass=0 - - self.traveldist=0 - self.traveltime=timer.getAbsTime() - self.position=self:GetCoordinate() - -- Radio parameters from template. self.radio.On=self.template.communication self.radio.Freq=self.template.frequency diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 25618611f..584fb865e 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -15,6 +15,7 @@ -- @type INTEL -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. -- @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. @@ -47,6 +48,7 @@ INTEL = { ClassName = "INTEL", Debug = nil, + verbose = 2, lid = nil, alias = nil, filterCategory = {}, @@ -355,7 +357,7 @@ function INTEL:onafterStatus(From, Event, To) self:I(self.lid..text) end - self:__Status(-30) + self:__Status(-60) end diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index f35f7e6df..e0a3af3e8 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -661,7 +661,7 @@ function TARGET:GetTargetVec3(Target) local object=Target.Object --Wrapper.Static#STATIC if object and object:IsAlive() then - return Target.Object:GetCoordinate() + return object:GetVec3() end elseif Target.Type==TARGET.ObjectType.AIRBASE then diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index fda0abe58..b12fa50e3 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -670,14 +670,15 @@ end -- @param #number UnitNumber The number of the UNIT wrapper class to be returned. -- @return Wrapper.Unit#UNIT The UNIT wrapper class. function GROUP:GetUnit( UnitNumber ) - self:F3( { self.GroupName, UnitNumber } ) local DCSGroup = self:GetDCSObject() if DCSGroup then + local DCSUnit = DCSGroup:getUnit( UnitNumber ) - local UnitFound = UNIT:Find( DCSGroup:getUnit( UnitNumber ) ) - self:T2( UnitFound ) + + local UnitFound = UNIT:Find(DCSUnit) + return UnitFound end @@ -690,13 +691,11 @@ end -- @param #number UnitNumber The number of the DCS Unit to be returned. -- @return DCS#Unit The DCS Unit. function GROUP:GetDCSUnit( UnitNumber ) - self:F3( { self.GroupName, UnitNumber } ) - local DCSGroup = self:GetDCSObject() + local DCSGroup=self:GetDCSObject() if DCSGroup then - local DCSUnitFound = DCSGroup:getUnit( UnitNumber ) - self:T3( DCSUnitFound ) + local DCSUnitFound=DCSGroup:getUnit( UnitNumber ) return DCSUnitFound end @@ -708,14 +707,14 @@ end -- @param #GROUP self -- @return #number The DCS Group size. function GROUP:GetSize() - self:F3( { self.GroupName } ) + local DCSGroup = self:GetDCSObject() if DCSGroup then + local GroupSize = DCSGroup:getSize() if GroupSize then - self:T3( GroupSize ) return GroupSize else return 0 From caef309547e70e25d5b7cdef4843d3fb372ce608 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 28 Aug 2020 13:24:29 +0200 Subject: [PATCH 52/79] Ops - Airboss optimizations WIP --- Moose Development/Moose/Core/Point.lua | 101 +++++++++++++++--- Moose Development/Moose/Core/Zone.lua | 42 +++++++- Moose Development/Moose/DCS.lua | 31 +++--- Moose Development/Moose/Ops/AirWing.lua | 18 ++-- Moose Development/Moose/Ops/Airboss.lua | 67 ++++++------ Moose Development/Moose/Ops/Auftrag.lua | 53 ++++++++- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 4 +- Moose Development/Moose/Ops/Squadron.lua | 9 +- Moose Development/Moose/Ops/Target.lua | 12 ++- Moose Development/Moose/Wrapper/Airbase.lua | 19 ++++ Moose Development/Moose/Wrapper/Group.lua | 25 +++-- .../Moose/Wrapper/Positionable.lua | 86 +++++++-------- 13 files changed, 331 insertions(+), 138 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 34ebf940f..49c33183b 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -300,6 +300,45 @@ do -- COORDINATE return { x = self.x, y = self.z } end + --- Update x,y,z coordinates from a given 3D vector. + -- @param #COORDINATE self + -- @param DCS#Vec3 Vec3 The 3D vector with x,y,z components. + -- @return #COORDINATE The modified COORDINATE itself. + function COORDINATE:UpdateFromVec3(Vec3) + + self.x=Vec3.x + self.y=Vec3.y + self.z=Vec3.z + + return self + end + + --- Update x,y,z coordinates from another given COORDINATE. + -- @param #COORDINATE self + -- @param #COORDINATE Coordinate The coordinate with the new x,y,z positions. + -- @return #COORDINATE The modified COORDINATE itself. + function COORDINATE:UpdateFromCoordinate(Coordinate) + + self.x=Coordinate.x + self.y=Coordinate.y + self.z=Coordinate.z + + return self + end + + --- Update x and z coordinates from a given 2D vector. + -- @param #COORDINATE self + -- @param DCS#Vec2 Vec2 The 2D vector with x,y components. x is overwriting COORDINATE.x while y is overwriting COORDINATE.z. + -- @return #COORDINATE The modified COORDINATE itself. + function COORDINATE:UpdateFromVec2(Vec2) + + self.x=Vec2.x + self.z=Vec2.y + + return self + end + + --- Returns the coordinate from the latitude and longitude given in decimal degrees. -- @param #COORDINATE self -- @param #number latitude Latitude in decimal degrees. @@ -503,19 +542,27 @@ do -- COORDINATE -- @param DCS#Distance Distance The Distance to be added in meters. -- @param DCS#Angle Angle The Angle in degrees. Defaults to 0 if not specified (nil). -- @param #boolean Keepalt If true, keep altitude of original coordinate. Default is that the new coordinate is created at the translated land height. - -- @return Core.Point#COORDINATE The new calculated COORDINATE. - function COORDINATE:Translate( Distance, Angle, Keepalt ) - local SX = self.x - local SY = self.z - local Radians = (Angle or 0) / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TY = Distance * math.sin( Radians ) + SY - - if Keepalt then - return COORDINATE:NewFromVec3( { x = TX, y=self.y, z = TY } ) + -- @param #boolean Overwrite If true, overwrite the original COORDINATE with the translated one. Otherwise, create a new COODINATE. + -- @return #COORDINATE The new calculated COORDINATE. + function COORDINATE:Translate( Distance, Angle, Keepalt, Overwrite ) + + -- Angle in rad. + local alpha = math.rad((Angle or 0)) + + local x = Distance * math.cos(alpha) + self.x -- New x + local z = Distance * math.sin(alpha) + self.z -- New z + + local y=Keepalt and self.y or land.getHeight({x=x, y=z}) + + if Overwrite then + self.x=x + self.y=y + self.z=z + return self else - return COORDINATE:NewFromVec2( { x = TX, y = TY } ) + return COORDINATE:New(x, y, z) end + end --- Rotate coordinate in 2D (x,z) space. @@ -1356,7 +1403,7 @@ do -- COORDINATE -- @param #number Coalition (Optional) Coalition of the airbase. -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. -- @return #number Distance to the closest airbase in meters. - function COORDINATE:GetClosestAirbase(Category, Coalition) + function COORDINATE:GetClosestAirbase2(Category, Coalition) -- Get all airbases of the map. local airbases=AIRBASE.GetAllAirbases(Coalition) @@ -1389,6 +1436,36 @@ do -- COORDINATE return closest,distmin end + + --- Gets the nearest airbase with respect to the current coordinates. + -- @param #COORDINATE self + -- @param #number Category (Optional) Category of the airbase. Enumerator of @{Wrapper.Airbase#AIRBASE.Category}. + -- @param #number Coalition (Optional) Coalition of the airbase. + -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. + -- @return #number Distance to the closest airbase in meters. + function COORDINATE:GetClosestAirbase(Category, Coalition) + + local a=self:GetVec3() + + local distmin=math.huge + local airbase=nil + for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do + local b=DCSairbase:getPoint() + + local c=UTILS.VecSubstract(a,b) + local dist=UTILS.VecNorm(c) + + --env.info(string.format("Airbase %s dist=%d category=%d", DCSairbase:getName(), dist, DCSairbase:getCategory())) + + if dist +-- @list Table of 2D vectors. + +--- A 3D points array. +-- @type ZONE_POLYGON_BASE.ListVec3 +-- @list Table of 3D vectors. --- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. @@ -1425,6 +1429,40 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) return self end +--- Update polygon points with an array of @{DCS#Vec2}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) + + self._.Polygon = {} + + for i=1,#Vec2Array do + self._.Polygon[i] = {} + self._.Polygon[i].x=Vec2Array[i].x + self._.Polygon[i].y=Vec2Array[i].y + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec3}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) + + self._.Polygon = {} + + for i=1,#Vec3Array do + self._.Polygon[i] = {} + self._.Polygon[i].x=Vec3Array[i].x + self._.Polygon[i].y=Vec3Array[i].z + end + + return self +end + --- Returns the center location of the polygon. -- @param #ZONE_GROUP self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index 206165bd3..15c73c29a 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -95,6 +95,11 @@ do -- world --- Returns a table of mark panels indexed numerically that are present within the mission. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_getMarkPanels) -- @function [parent=#world] getMarkPanels -- @return #table Table of marks. + + --- Returns a table of DCS airbase objects. + -- @function [parent=#world] getAirbases + -- @param #number coalitionId The coalition side number ID. Default is all airbases are returned. + -- @return #table Table of DCS airbase objects. end -- world @@ -360,7 +365,7 @@ do -- Types --- Time is given in seconds. -- @type Time - -- @extends #number + -- @extends #number Time in seconds. --- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. -- @type ModelTime @@ -368,20 +373,20 @@ do -- Types --- Mission time is a model time plus time of the mission start. -- @type MissionTime - -- @extends #number + -- @extends #number Time in seconds. --- Distance is given in meters. -- @type Distance - -- @extends #number + -- @extends #number Distance in meters. --- Angle is given in radians. -- @type Angle - -- @extends #number + -- @extends #number Angle in radians. --- Azimuth is an angle of rotation around world axis y counter-clockwise. -- @type Azimuth - -- @extends #number + -- @extends #number Angle in radians. --- Mass is given in kilograms. -- @type Mass @@ -401,15 +406,15 @@ do -- Types --- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: -- @type Position3 - -- @field #Vec3 p - -- @field #Vec3 x - -- @field #Vec3 y - -- @field #Vec3 z + -- @field #Vec3 p 3D position vector. + -- @field #Vec3 x Orientation component of vector pointing East. + -- @field #Vec3 y Orientation component of vector pointing up. + -- @field #Vec3 z Orientation component of vector pointing North. --- 3-dimensional box. -- @type Box3 - -- @field #Vec3 min - -- @field #Vec3 max + -- @field #Vec3 min Min. + -- @field #Vec3 max Max --- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". -- @type TypeName @@ -514,7 +519,7 @@ do -- Object --- Returns object coordinates for current time. -- @function [parent=#Object] getPoint -- @param #Object self - -- @return #Vec3 + -- @return #Vec3 3D position vector with x,y,z components. --- Returns object position for current time. -- @function [parent=#Object] getPosition @@ -524,7 +529,7 @@ do -- Object --- Returns the unit's velocity vector. -- @function [parent=#Object] getVelocity -- @param #Object self - -- @return #Vec3 + -- @return #Vec3 3D velocity vector. --- Returns true if the unit is in air. -- @function [parent=#Object] inAir diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 3169eb995..0acea986b 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -821,7 +821,7 @@ function AIRWING:onafterStatus(From, Event, To) -- Assets tot local Npq, Np, Nq=self:CountAssetsOnMission() - local assets=string.format("%d [Mission=%d (Active=%d, Queued=%d)]", self:CountAssets(), Npq, Np, Nq) + local assets=string.format("%d (OnMission: Total=%d, Active=%d, Queued=%d)", self:CountAssets(), Npq, Np, Nq) -- Output. local text=string.format("%s: Missions=%d, Payloads=%d (%d), Squads=%d, Assets=%s", fsmstate, Nmissions, Npayloads, #self.payloads, #self.squadrons, assets) @@ -836,7 +836,7 @@ function AIRWING:onafterStatus(From, Event, To) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - local prio=string.format("%d/%d", mission.prio, mission.importance) ; if mission.urgent then prio=prio.." (!)" end + local prio=string.format("%d/%s", mission.prio, tostring(mission.importance)) ; if mission.urgent then prio=prio.." (!)" end local assets=string.format("%d/%d", mission:CountOpsGroups(), mission.nassets) local target=string.format("%d/%d Damage=%.1f", mission:CountMissionTargets(), mission:GetTargetInitialNumber(), mission:GetTargetDamage()) @@ -1030,7 +1030,9 @@ function AIRWING:CheckRescuhelo() local N=self:CountMissionsInQueue({AUFTRAG.Type.RESCUEHELO}) - local carrier=UNIT:FindByName(self.airbase:GetName()) + local name=self.airbase:GetName() + + local carrier=UNIT:FindByName(name) for i=1,self.nflightsRescueHelo-N do @@ -1127,7 +1129,7 @@ function AIRWING:_GetNextMission() local vip=math.huge for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - if mission.importance return") - --return - end - -- Get current time. local time=timer.getTime() -- Update marshal and pattern queue every 30 seconds. if time-self.Tqueue>self.dTqueue then - --collectgarbage() - -- Get time. local clock=UTILS.SecondsToClock(timer.getAbsTime()) local eta=UTILS.SecondsToClock(self:_GetETAatNextWP()) @@ -3414,7 +3412,7 @@ function AIRBOSS:onafterStatus(From, Event, To) local speed=self.carrier:GetVelocityKNOTS() -- Check water is ahead. - local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) + local collision=false --self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg)) local holdtime=0 if self.holdtimestamp then @@ -3470,15 +3468,9 @@ function AIRBOSS:onafterStatus(From, Event, To) -- Disable turn into the wind for this window so that we do not do this all over again. self.recoverywindow.WIND=false end - - else - - -- Find path around the obstacle. - if not self.detour then - --self:_Pathfinder() - end - + end + end @@ -10287,24 +10279,25 @@ function AIRBOSS:_GetSternCoord() local FB=self:GetFinalBearing() -- Stern coordinate (sterndist<0). Also translate 10 meters starboard wrt Final bearing. - local stern=self:GetCoordinate() + self.sterncoord:UpdateFromCoordinate(self:GetCoordinate()) + --local stern=self:GetCoordinate() -- Stern coordinate (sterndist<0). if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Tarawa: Translate 8 meters port. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(8, FB-90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8, FB-90, true, true) elseif self.carriertype==AIRBOSS.CarrierType.STENNIS then -- Stennis: translate 7 meters starboard wrt Final bearing. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(7, FB+90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(7, FB+90, true, true) else -- Nimitz SC: translate 8 meters starboard wrt Final bearing. - stern=stern:Translate(self.carrierparam.sterndist, hdg):Translate(8.5, FB+90) + self.sterncoord:Translate(self.carrierparam.sterndist, hdg, true, true):Translate(8.5, FB+90, true, true) end -- Set altitude. - stern:SetAltitude(self.carrierparam.deckheight) + self.sterncoord:SetAltitude(self.carrierparam.deckheight) - return stern + return self.sterncoord end --- Get wire from landing position. @@ -11339,9 +11332,12 @@ end -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Optimal landing coordinate. function AIRBOSS:_GetOptLandingCoordinate() + + -- Start with stern coordiante. + self.landingcoord:UpdateFromCoordinate(self:_GetSternCoord()) -- Stern coordinate. - local stern=self:_GetSternCoord() + --local stern=self:_GetSternCoord() -- Final bearing. local FB=self:GetFinalBearing(false) @@ -11349,10 +11345,11 @@ function AIRBOSS:_GetOptLandingCoordinate() if self.carriertype==AIRBOSS.CarrierType.TARAWA then -- Landing 100 ft abeam, 120 ft alt. - stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) + self.landingcoord:UpdateFromCoordinate(self:_GetLandingSpotCoordinate()):Translate(35, FB-90, true, true) + --stern=self:_GetLandingSpotCoordinate():Translate(35, FB-90) -- Alitude 120 ft. - stern:SetAltitude(UTILS.FeetToMeters(120)) + self.landingcoord:SetAltitude(UTILS.FeetToMeters(120)) else @@ -11360,15 +11357,15 @@ function AIRBOSS:_GetOptLandingCoordinate() if self.carrierparam.wire3 then -- We take the position of the 3rd wire to approximately account for the length of the aircraft. local w3=self.carrierparam.wire3 - stern=stern:Translate(w3, FB, true) + self.landingcoord:Translate(w3, FB, true, true) end -- Add 2 meters to account for aircraft height. - stern.y=stern.y+2 + self.landingcoord.y=self.landingcoord.y+2 end - return stern + return self.landingcoord end --- Get landing spot on Tarawa. @@ -11376,8 +11373,10 @@ end -- @return Core.Point#COORDINATE Primary landing spot coordinate. function AIRBOSS:_GetLandingSpotCoordinate() + self.landingspotcoord:UpdateFromCoordinate(self:_GetSternCoord()) + -- Stern coordinate. - local stern=self:_GetSternCoord() + --local stern=self:_GetSternCoord() if self.carriertype==AIRBOSS.CarrierType.TARAWA then @@ -11385,11 +11384,11 @@ function AIRBOSS:_GetLandingSpotCoordinate() local hdg=self:GetHeading() -- Primary landing spot 7.5 - stern=stern:Translate(57, hdg):SetAltitude(self.carrierparam.deckheight) + self.landingspotcoord:Translate(57, hdg, true, true):SetAltitude(self.carrierparam.deckheight) end - return stern + return self.landingspotcoord end --- Get true (or magnetic) heading of carrier. @@ -14333,7 +14332,7 @@ end -- @param #AIRBOSS self -- @return Core.Point#COORDINATE Carrier coordinate. function AIRBOSS:GetCoordinate() - return self.carrier:GetCoordinate() + return self.carrier:GetCoord() end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 16edd0aef..ce6fda25c 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1096,7 +1096,9 @@ function AUFTRAG:NewRESCUEHELO(Carrier) local mission=AUFTRAG:New(AUFTRAG.Type.RESCUEHELO) - self.carrier=Carrier + --mission.carrier=Carrier + + mission:_TargetFromObject(Carrier) -- Mission options: mission.missionTask=ENUMS.MissionTask.NOTHING @@ -1412,12 +1414,12 @@ end -- @param #AUFTRAG self -- @param #number Prio Priority 1=high, 100=low. Default 50. -- @param #boolean Urgent If *true*, another running mission might be cancelled if it has a lower priority. --- @param #number Importance Number 1-10. If missions with lower value are in the queue, these have to be finished first. Default is 5. +-- @param #number Importance Number 1-10. If missions with lower value are in the queue, these have to be finished first. Default is `nil`. -- @return #AUFTRAG self function AUFTRAG:SetPriority(Prio, Urgent, Importance) self.prio=Prio or 50 self.urgent=Urgent - self.importance=Importance or 5 + self.importance=Importance return self end @@ -1665,6 +1667,47 @@ function AUFTRAG:SetICLS(Channel, Morse, UnitName) return self end +--- Get mission type. +-- @param #AUFTRAG self +-- @return #string Mission type, e.g. "BAI". +function AUFTRAG:GetType() + return self.type +end + +--- Get mission name. +-- @param #AUFTRAG self +-- @return #string Mission name, e.g. "Auftrag Nr.1". +function AUFTRAG:GetName() + return self.name +end + +--- Get number of required assets. +-- @param #AUFTRAG self +-- @return #number Numer of required assets. +function AUFTRAG:GetNumberOfRequiredAssets() + return self.nassets +end + +--- Get mission priority. +-- @param #AUFTRAG self +-- @return #number Priority. Smaller is higher. +function AUFTRAG:GetPriority() + return self.prio +end + +--- Check if mission is "urgent". +-- @param #AUFTRAG self +-- @return #boolean If `true`, mission is "urgent". +function AUFTRAG:IsUrgent() + return self.urgent +end + +--- Get mission importance. +-- @param #AUFTRAG self +-- @return #number Importance. Smaller is higher. +function AUFTRAG:GetImportance() + return self.importance +end --- Add start condition. -- @param #AUFTRAG self @@ -2599,7 +2642,7 @@ function AUFTRAG:onafterSuccess(From, Event, To) self.status=AUFTRAG.Status.SUCCESS self:T(self.lid..string.format("New mission status=%s", self.status)) - local repeatme=self.repeatedFailure Date: Sat, 29 Aug 2020 20:26:20 +0200 Subject: [PATCH 53/79] TIMER - Added new TIMER class. --- Moose Development/Moose/Core/Fsm.lua | 7 +- Moose Development/Moose/Core/Point.lua | 18 +- Moose Development/Moose/Core/Timer.lua | 250 ++++++++++++++++++++ Moose Development/Moose/Core/Zone.lua | 57 ++++- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/Airboss.lua | 110 ++++++--- Moose Development/Moose/Ops/ArmyGroup.lua | 2 +- Moose Development/Moose/Ops/Auftrag.lua | 6 +- Moose Development/Moose/Ops/FlightGroup.lua | 2 + Moose Development/Moose/Ops/NavyGroup.lua | 2 +- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Ops/Squadron.lua | 4 +- Moose Development/Moose/Ops/Target.lua | 2 +- 13 files changed, 397 insertions(+), 66 deletions(-) create mode 100644 Moose Development/Moose/Core/Timer.lua diff --git a/Moose Development/Moose/Core/Fsm.lua b/Moose Development/Moose/Core/Fsm.lua index 70275d945..80dfcb0c5 100644 --- a/Moose Development/Moose/Core/Fsm.lua +++ b/Moose Development/Moose/Core/Fsm.lua @@ -619,6 +619,7 @@ do -- FSM if self[handler] then + --[[ if step == "onafter" or step == "OnAfter" then self:T( ":::>" .. step .. params[2] .. " : " .. params[1] .. " >> " .. params[2] .. ">" .. step .. params[2] .. "()" .. " >> " .. params[3] ) elseif step == "onbefore" or step == "OnBefore" then @@ -630,6 +631,7 @@ do -- FSM else self:T( ":::>" .. step .. " : " .. params[1] .. " >> " .. params[2] .. " >> " .. params[3] ) end + ]] self._EventSchedules[EventName] = nil @@ -930,9 +932,10 @@ do -- FSM -- @return #boolean If true, FSM can do the event. -- @return #string To state. function FSM:can(e) + local Event = self.Events[e] - - self:F3( { self.current, Event } ) + + --self:F3( { self.current, Event } ) local To = Event and Event.map[self.current] or Event.map['*'] diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 49c33183b..97f0fb280 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -560,6 +560,7 @@ do -- COORDINATE self.z=z return self else + --env.info("FF translate with NEW coordinate T="..timer.getTime()) return COORDINATE:New(x, y, z) end @@ -1136,23 +1137,6 @@ do -- COORDINATE return self end - --- Add a Distance in meters from the COORDINATE horizontal plane, with the given angle, and calculate the new COORDINATE. - -- @param #COORDINATE self - -- @param DCS#Distance Distance The Distance to be added in meters. - -- @param DCS#Angle Angle The Angle in degrees. - -- @return #COORDINATE The new calculated COORDINATE. - function COORDINATE:Translate( Distance, Angle ) - local SX = self.x - local SZ = self.z - local Radians = Angle / 180 * math.pi - local TX = Distance * math.cos( Radians ) + SX - local TZ = Distance * math.sin( Radians ) + SZ - - return COORDINATE:New( TX, self.y, TZ ) - end - - - --- Build an air type route point. -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua new file mode 100644 index 000000000..cbf739fa3 --- /dev/null +++ b/Moose Development/Moose/Core/Timer.lua @@ -0,0 +1,250 @@ +--- **Core** - Timer scheduler. +-- +-- **Main Features:** +-- +-- * Delay function calls +-- * Easy set up and little overhead +-- * Set start, stop and time interval +-- * Define max function calls +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Core.Timer +-- @image CORE_Timer.png + + +--- TIMER class. +-- @type TIMER +-- @field #string ClassName Name of the class. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number tid Timer ID returned by the DCS API function. +-- @field #function func Timer function. +-- @field #table para Parameters passed to the timer function. +-- @field #number Tstart Relative start time in seconds. +-- @field #number Tstop Relative stop time in seconds. +-- @field #number dT Time interval between function calls in seconds. +-- @field #number ncalls Counter of function calls. +-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped. +-- @extends Core.Base#BASE + +--- *Better three hours too soon than a minute too late.* – William Shakespeare +-- +-- === +-- +-- ![Banner Image](..\Presentations\Timer\TIMER_Main.jpg) +-- +-- # The TIMER Concept +-- +-- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. +-- +-- # Construction +-- +-- A new TIMER is created by the @{#TIMER.New}(*func*, *...*) function +-- +-- local mytimer=TIMER:New(myfunction, a, b) +-- +-- The first parameter *func* is the function that is called followed by the necessary comma separeted parameters that are passed to that function. +-- +-- ## Starting the Timer +-- +-- The timer is started by the @{#TIMER.Start}(*Tstart*, *dT*, *Duration*) function +-- +-- mytimer:Start(5, 1, 20) +-- +-- where +-- +-- * *Tstart* is the relative start time in seconds. In the example, the first function call happens after 5 sec. +-- * *dT* is the time interval between function calls in seconds. Above, the function is called once per second. +-- * *Duration* is the duration in seconds after which the timer is stopped. This is relative to the start time. Here, the timer will run for 20 seconds. +-- +-- Note that +-- +-- * if *Tstart* is not specified (*nil*), the first function call happens immediately. +-- * if *dT* is not specified (*nil*), the function is called only once. +-- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below). +-- +-- For example, +-- +-- mytimer:Start(3) -- Will call the function once after 3 seconds. +-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternaty. +-- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec. +-- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway. +-- +-- ## Stopping the Timer +-- +-- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function +-- +-- mytimer:Stop() +-- +-- If the optional paramter *Delay* is specified, the timer is stopped after *delay* seconds. +-- +-- ## Limit Function Calls +-- +-- The timer can be stopped after a certain amount of function calles with the @{#TIMER.SetMaxFunctionCalls}(*Nmax*) function +-- +-- mytimer:SetMaxFunctionCalls(20) +-- +-- where *Nmax* is the number of calls after which the timer is stopped, here 20. +-- +-- For example, +-- +-- mytimer:SetMaxFunctionCalls(66):Start(1.0, 0.1) +-- +-- will start the timer after one second and call the function every 0.1 seconds. Once the function has been called 66 times, the timer is stopped. +-- +-- +-- @field #TIMER +TIMER = { + ClassName = "TIMER", + lid = nil, +} + +--- TIMER class version. +-- @field #string version +TIMER.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot. +-- TODO: Write docs. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new TIMER object. +-- @param #TIMER self +-- @param #function Function The function to call. +-- @param ... Parameters passed to the function if any. +-- @return #TIMER self +function TIMER:New(Function, ...) + + -- Inherit BASE. + local self=BASE:Inherit(self, BASE:New()) --#TIMER + + self.lid="TIMER | " + + -- Function to call. + self.func=Function + + -- Function arguments. + self.para=arg or {} + + -- Number of function calls. + self.ncalls=0 + + return self +end + +--- Create a new TIMER object. +-- @param #TIMER self +-- @param #number Tstart Relative start time in seconds. +-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once. +-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function. +-- @return #TIMER self +function TIMER:Start(Tstart, dT, Duration) + + -- Current time. + local Tnow=timer.getTime() + + -- Start time in sec. + self.Tstart=Tstart or Tnow + + -- Set time interval. + self.dT=dT + + -- Stop time. + if Duration then + self.Tstop=self.Tstart+Duration + end + + -- Call DCS timer function. + self.tid=timer.scheduleFunction(TIMER._Function, self, self.Tstart) + + -- Set log id. + self.lid=string.format("TIMER ID=%d | ", self.tid) + + -- Debug info. + self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop))) + + return self +end + +--- Stop the timer by removing the timer function. +-- @param #TIMER self +-- @param #number Delay (Optional) Delay in seconds, before the timer is stopped. +-- @return #TIMER self +function TIMER:Stop(Delay) + + if Delay and Delay>0 then + + self.Tstop=timer.getTime()+Delay + + else + + if self.tid then + self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) + timer.removeFunction(self.tid) + end + + end + + return self +end + +--- Set max number of function calls. When the function has been called this many times, the TIMER is stopped. +-- @param #TIMER self +-- @param #number Nmax Set number of max function calls. +-- @return #TIMER self +function TIMER:SetMaxFunctionCalls(Nmax) + self.ncallsMax=Nmax + return self +end + +--- Call timer function. +-- @param #TIMER self +-- @param #number time DCS model time in seconds. +-- @return #number Time when the function is called again or `nil` if the timer is stopped. +function TIMER:_Function(time) + + -- Call function. + self.func(unpack(self.para)) + + -- Increase number of calls. + self.ncalls=self.ncalls+1 + + -- Next time. + local Tnext=self.dT and time+self.dT or nil + + -- Check if we stop the timer. + local stopme=false + if Tnext==nil then + -- No next time. + self:T(self.lid..string.format("No next time as dT=nil ==> Stopping timer after %d function calls", self.ncalls)) + stopme=true + elseif self.Tstop and Tnext>self.Tstop then + -- Stop time passed. + self:T(self.lid..string.format("Stop time passed %.3f > %.3f ==> Stopping timer after %d function calls", Tnext, self.Tstop, self.ncalls)) + stopme=true + elseif self.ncallsMax and self.ncalls>=self.ncallsMax then + -- Number of max function calls reached. + self:T(self.lid..string.format("Max function calls Nmax=%d reached ==> Stopping timer after %d function calls", self.ncallsMax, self.ncalls)) + stopme=true + end + + if stopme then + -- Remove timer function. + self:Stop() + return nil + else + -- Call again in Tnext seconds. + return Tnext + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 0576da9aa..feda32b72 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -443,6 +443,41 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) return self end +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec2 Vec2 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius) + + -- New center of the zone. + self.Vec2=Vec2 + + if Radius then + self.Radius=Radius + end + + return self +end + +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec3 Vec3 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius) + + -- New center of the zone. + self.Vec2.x=Vec3.x + self.Vec2.y=Vec3.z + + if Radius then + self.Radius=Radius + end + + return self +end + --- Mark the zone with markers on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Points (Optional) The amount of points in the circle. Default 360. @@ -1410,20 +1445,24 @@ ZONE_POLYGON_BASE = { -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) self:F( { ZoneName, PointsArray } ) - local i = 0 - - self._.Polygon = {} - - for i = 1, #PointsArray do - self._.Polygon[i] = {} - self._.Polygon[i].x = PointsArray[i].x - self._.Polygon[i].y = PointsArray[i].y + if PointsArray then + + self._.Polygon = {} + + for i = 1, #PointsArray do + self._.Polygon[i] = {} + self._.Polygon[i].x = PointsArray[i].x + self._.Polygon[i].y = PointsArray[i].y + end + end return self diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index f74335862..f1b6d1145 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -25,6 +25,7 @@ __Moose.Include( 'Scripts/Moose/Core/RadioQueue.lua' ) __Moose.Include( 'Scripts/Moose/Core/RadioSpeech.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spawn.lua' ) __Moose.Include( 'Scripts/Moose/Core/SpawnStatic.lua' ) +__Moose.Include( 'Scripts/Moose/Core/Timer.lua' ) __Moose.Include( 'Scripts/Moose/Core/Goal.lua' ) __Moose.Include( 'Scripts/Moose/Core/Spot.lua' ) __Moose.Include( 'Scripts/Moose/Core/Astar.lua' ) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 5765e02da..feb293680 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3385,6 +3385,11 @@ function AIRBOSS:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) self:HandleEvent(EVENTS.MissionEnd) + --self.StatusScheduler=SCHEDULER:New(self) + --self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) + + self.StatusTimer=TIMER:New(self._Status, self):Start(2, 0.5) + -- Start status check in 1 second. self:__Status(1) end @@ -3501,14 +3506,21 @@ function AIRBOSS:onafterStatus(From, Event, To) self:_ActivateBeacons() end + -- Call status every ~0.5 seconds. + self:__Status(-30) + +end + +--- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? +-- @param #AIRBOSS self +function AIRBOSS:_Status() + -- Check player status. self:_CheckPlayerStatus() -- Check AI landing pattern status self:_CheckAIStatus() - -- Call status every ~0.5 seconds. - self:__Status(-self.dTstatus) end --- Check AI status. Pattern queue AI in the groove? Marshal queue AI arrived in holding zone? @@ -3559,12 +3571,16 @@ function AIRBOSS:_CheckAIStatus() -- Get lineup and distance to carrier. local lineup=self:_Lineup(unit, true) + + local unitcoord=unit:GetCoord() + + local dist=unitcoord:Get2DDistance(self:GetCoord()) -- Distance in NM. - local distance=UTILS.MetersToNM(unit:GetCoordinate():Get2DDistance(self:GetCoordinate())) + local distance=UTILS.MetersToNM(dist) -- Altitude in ft. - local alt=UTILS.MetersToFeet(unit:GetAltitude()) + local alt=UTILS.MetersToFeet(unitcoord.y) -- Check if parameters are right and flight is in the groove. if lineup<2 and distance<=0.75 and alt<500 and not element.ballcall then @@ -10497,6 +10513,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. function AIRBOSS:_GetZoneInitial(case) + self.zoneInitial=self.zoneInitial or ZONE_POLYGON_BASE:New("Zone CASE I/II Initial") + -- Get radial, i.e. inverse of BRC. local radial=self:GetRadial(2, false, false) @@ -10504,7 +10522,7 @@ function AIRBOSS:_GetZoneInitial(case) local cv=self:GetCoordinate() -- Vec2 array. - local vec2 + local vec2={} if case==1 then -- Case I @@ -10535,9 +10553,12 @@ function AIRBOSS:_GetZoneInitial(case) end -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) + --local zone=ZONE_POLYGON_BASE:New("Zone CASE I/II Initial", vec2) + + self.zoneInitial:UpdateFromVec2(vec2) - return zone + --return zone + return self.zoneInitial end --- Get lineup groove zone. @@ -10545,6 +10566,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone. function AIRBOSS:_GetZoneLineup() + self.zoneLineup=self.zoneLineup or ZONE_POLYGON_BASE:New("Zone Lineup") + -- Get radial, i.e. inverse of BRC. local fbi=self:GetRadial(1, false, false) @@ -10560,11 +10583,14 @@ function AIRBOSS:_GetZoneLineup() -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} + + self.zoneLineup:UpdateFromVec2(vec2) -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) - - return zone + --local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + --return zone + + return self.zoneLineup end @@ -10576,6 +10602,8 @@ end -- @return Core.Zone#ZONE_POLYGON_BASE Groove zone. function AIRBOSS:_GetZoneGroove(l, w, b) + self.zoneGroove=self.zoneGroove or ZONE_POLYGON_BASE:New("Zone Groove") + l=l or 1.50 w=w or 0.25 b=b or 0.10 @@ -10596,11 +10624,14 @@ function AIRBOSS:_GetZoneGroove(l, w, b) -- Vec2 array. local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2(), c6:GetVec2()} + + self.zoneGroove:UpdateFromVec2(vec2) -- Polygon zone. - local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) - - return zone + --local zone=ZONE_POLYGON_BASE:New("Zone Groove", vec2) + --return zone + + return self.zoneGroove end --- Get Bullseye zone with radius 1 NM and DME 3 NM from the carrier. Radial depends on recovery case. @@ -10624,8 +10655,9 @@ function AIRBOSS:_GetZoneBullseye(case) -- Create zone. local zone=ZONE_RADIUS:New("Zone Bullseye", vec2, radius) - return zone + + --self.zoneBullseye=self.zoneBullseye or ZONE_RADIUS:New("Zone Bullseye", vec2, radius) end --- Get dirty up zone with radius 1 NM and DME 9 NM from the carrier. Radial depends on recovery case. @@ -10821,6 +10853,8 @@ end -- @return Core.Zone#ZONE Zone surrounding the carrier. function AIRBOSS:_GetZoneCarrierBox() + self.zoneCarrierbox=self.zoneCarrierbox or ZONE_POLYGON_BASE:New("Carrier Box Zone") + -- Stern coordinate. local S=self:_GetSternCoord() @@ -10849,9 +10883,12 @@ function AIRBOSS:_GetZoneCarrierBox() end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) - - return zone + --local zone=ZONE_POLYGON_BASE:New("Carrier Box Zone", vec2) + --return zone + + self.zoneCarrierbox:UpdateFromVec2(vec2) + + return self.zoneCarrierbox end --- Get zone of landing runway. @@ -10859,6 +10896,8 @@ end -- @return Core.Zone#ZONE_POLYGON Zone surrounding landing runway. function AIRBOSS:_GetZoneRunwayBox() + self.zoneRunwaybox=self.zoneRunwaybox or ZONE_POLYGON_BASE:New("Landing Runway Zone") + -- Stern coordinate. local S=self:_GetSternCoord() @@ -10881,9 +10920,12 @@ function AIRBOSS:_GetZoneRunwayBox() end -- Create polygon zone. - local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) - - return zone + --local zone=ZONE_POLYGON_BASE:New("Landing Runway Zone", vec2) + --return zone + + self.zoneRunwaybox:UpdateFromVec2(vec2) + + return self.zoneRunwaybox end @@ -10986,12 +11028,14 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- Post 2.5 NM port of carrier. local Post=self:GetCoordinate():Translate(D, hdg+270) + --TODO: update zone not creating a new one. + -- Create holding zone. - zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) + self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", Post:GetVec2(), self.marshalradius) -- Delta pattern. if self.carriertype==AIRBOSS.CarrierType.TARAWA then - zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) + self.zoneHolding=ZONE_RADIUS:New("CASE I Holding Zone", self.carrier:GetVec2(), UTILS.NMToMeters(5)) end @@ -11010,10 +11054,12 @@ function AIRBOSS:_GetZoneHolding(case, stack) -- Square zone length=7NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier. -- So stay 0-5 NM (+1 NM error margin) port of carrier. - zoneHolding=ZONE_POLYGON_BASE:New("CASE II/III Holding Zone", p) + self.zoneHolding=self.zoneHolding or ZONE_POLYGON_BASE:New("CASE II/III Holding Zone") + + self.zoneHolding:UpdateFromVec2(p) end - return zoneHolding + return self.zoneHolding end --- Get zone where player are automatically commence when enter. @@ -11054,7 +11100,9 @@ function AIRBOSS:_GetZoneCommence(case, stack) end -- Create holding zone. - zone=ZONE_RADIUS:New("CASE I Commence Zone", Three:GetVec2(), R) + self.zoneCommence=self.zoneCommence or ZONE_RADIUS:New("CASE I Commence Zone") + + self.zoneCommence:UpdateFromVec2(Three:GetVec2(), R) else -- Case II/III @@ -11085,11 +11133,13 @@ function AIRBOSS:_GetZoneCommence(case, stack) end -- Zone polygon. - zone=ZONE_POLYGON_BASE:New("CASE II/III Commence Zone", p) + self.zoneCommence=self.zoneCommence or ZONE_POLYGON_BASE:New("CASE II/III Commence Zone") + + self.zoneCommence:UpdateFromVec2(p) end - return zone + return self.zoneCommence end ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -14335,6 +14385,12 @@ function AIRBOSS:GetCoordinate() return self.carrier:GetCoord() end +--- Get carrier coordinate. +-- @param #AIRBOSS self +-- @return Core.Point#COORDINATE Carrier coordinate. +function AIRBOSS:GetCoord() + return self.carrier:GetCoord() +end --- Get static weather of this mission from env.mission.weather. -- @param #AIRBOSS self diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 2593978ee..f5e92a8ef 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -40,7 +40,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="0.0.1" +ARMYGROUP.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index ce6fda25c..6ce63f05a 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1772,13 +1772,9 @@ end --- Assign airwing squadron(s) to the mission. Only these squads will be considered for the job. -- @param #AUFTRAG self --- @param #table Squadrons A table of SQUADRONs or a single SQUADRON object. +-- @param #table Squadrons A table of SQUADRON(s). **Has to be a table {}** even if a single squad is given. -- @return #AUFTRAG self function AUFTRAG:AssignSquadrons(Squadrons) - - if Squadrons:IsInstanceOf("SQUADRON") then - Squadrons={Squadrons} - end for _,_squad in pairs(Squadrons) do local squadron=_squad --Ops.Squadron#SQUADRON diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 44da6ed34..dc1911eda 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -332,6 +332,8 @@ function FLIGHTGROUP:New(group) self:__Status(-2) self:__QueueUpdate(-3) + --self.Timer=SCHEDULER:New() + return self end diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9aeec162b..9312b19c2 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -66,7 +66,7 @@ NAVYGROUP = { --- NavyGroup version. -- @field #string version -NAVYGROUP.version="0.1.0" +NAVYGROUP.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 578830e22..c4358afbf 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -262,7 +262,7 @@ OPSGROUP.TaskType={ --- NavyGroup version. -- @field #string version -OPSGROUP.version="0.2.0" +OPSGROUP.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index e21e681c1..67f67419c 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -37,10 +37,10 @@ -- @field #number callsigncounter Counter to increase callsign names for new assets. -- @field Ops.AirWing#AIRWING airwing The AIRWING object the squadron belongs to. -- @field #number Ngroups Number of asset flight groups this squadron has. --- @field #number engageRange Engagement range in meters. +-- @field #number engageRange Mission range in meters. -- @field #string attribute Generalized attribute of the squadron template group. -- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil. --- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probpe=1). Default nil. +-- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probe=1). Default nil. -- @field #table tacanChannel List of TACAN channels available to the squadron. -- @field #number radioFreq Radio frequency in MHz the squad uses. -- @field #number radioModu Radio modulation the squad uses. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 80fdb9497..a4e44a61f 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -110,7 +110,7 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.0.1" +TARGET.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list From a126906776f20b8e876c2d42257d39d2ee8e932e Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 29 Aug 2020 21:24:15 +0200 Subject: [PATCH 54/79] up --- Moose Development/Moose/Wrapper/Group.lua | 39 ------------------- .../Moose/Wrapper/Positionable.lua | 6 +-- 2 files changed, 3 insertions(+), 42 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 4a6798bbb..7ceb235af 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2245,45 +2245,6 @@ function GROUP:GetDCSDesc(n) return nil end ---- Get health of the group. --- @param #GROUP self --- @return #number Health in percent. -function GROUP:GetHealth() - - local lp=0 - local lp0=0 - - local units=self:GetUnits() - - for _,_unit in pairs(units or {}) do - local unit=_unit --Wrapper.Unit#UNIT - - if unit and unit:IsAlive() then - local life=unit:GetLife() - local life0=unit:GetLife0() - life0=math.max(life0, life) --Issue with ships - - lp=lp+life - lp0=lp0+life - - end - - end - - if lp0>0 then - return lp/lp0*100 - else - return 0 - end -end - ---- Get damage of the group. --- @param #GROUP self --- @return #number Damage in percent. -function GROUP:GetDamage() - return 100-self:GetHealth() -end - --- Get the generalized attribute of a self. -- Note that for a heterogenious self, the attribute is determined from the attribute of the first unit! diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index f1a4ba934..5097935bb 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -304,8 +304,7 @@ function POSITIONABLE:GetPointVec3() -- Get 3D vector. local PositionableVec3 = self:GetPositionVec3() - if self.pointvec3 then - --env.info("FF GetCoordinate GOT for "..tostring(self.PositionableName)) + if false and self.pointvec3 then -- Update vector. self.pointvec3.x=PositionableVec3.x @@ -313,9 +312,10 @@ function POSITIONABLE:GetPointVec3() self.pointvec3.z=PositionableVec3.z else - --env.info("FF GetCoordinate NEW for "..tostring(self.PositionableName)) + -- Create a new POINT_VEC3 object. self.pointvec3=POINT_VEC3:NewFromVec3(PositionableVec3) + end return self.pointvec3 From 7e73db505bef6fb1492eca4bf0965f88429a2f98 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 Aug 2020 01:38:18 +0200 Subject: [PATCH 55/79] Decreased Tracing --- Moose Development/Moose/Ops/AirWing.lua | 60 ++++++++-------- Moose Development/Moose/Ops/FlightGroup.lua | 79 +++++++++++---------- Moose Development/Moose/Ops/OpsGroup.lua | 58 ++++++--------- Moose Development/Moose/Ops/Squadron.lua | 36 +++++++--- 4 files changed, 120 insertions(+), 113 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 0acea986b..5ea8ffe5a 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -154,7 +154,7 @@ AIRWING = { --- AIRWING class version. -- @field #string version -AIRWING.version="0.3.0" +AIRWING.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -204,7 +204,7 @@ function AIRWING:New(warehousename, airwingname) self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. -- Defaults: - self:SetVerbosity(2) + self:SetVerbosity(0) self.nflightsCAP=0 self.nflightsAWACS=0 self.nflightsTANKERboom=0 @@ -233,15 +233,6 @@ function AIRWING:New(warehousename, airwingname) -- @param #AIRWING self -- @param #number delay Delay in seconds. - - -- Debug trace. - if false then - self.Debug=true - self:TraceOnOff(true) - self:TraceClass(self.ClassName) - self:TraceLevel(1) - end - return self end @@ -341,7 +332,7 @@ function AIRWING:NewPayload(Unit, Npayloads, MissionTypes, Performance) end -- Info - self:I(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", + self:T(self.lid..string.format("Adding new payload from unit %s for aircraft type %s: ID=%d, N=%d (unlimited=%s), performance=%d, missions: %s", payload.unitname, payload.aircrafttype, payload.uid, payload.navail, tostring(payload.unlimited), Performance, table.concat(MissionTypes, ", "))) -- Add payload @@ -398,7 +389,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) -- Quick check if we have any payloads. if not self.payloads or #self.payloads==0 then - self:I(self.lid.."WARNING: No payloads in stock!") + self:T(self.lid.."WARNING: No payloads in stock!") return nil end @@ -466,7 +457,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) -- Debug. if self.Debug then - self:I(self.lid..string.format("FF Sorted payloads for mission type X and aircraft type=Y:")) + self:I(self.lid..string.format("Sorted payloads for mission type X and aircraft type=Y:")) for _,_payload in ipairs(self.payloads) do local payload=_payload --#AIRWING.Payload if payload.aircrafttype==UnitType and self:CheckMissionCapability(MissionType, payload.capabilities) then @@ -608,7 +599,7 @@ function AIRWING:AddMission(Mission) -- Info text. local text=string.format("Added mission %s (type=%s). Starting at %s. Stopping at %s", tostring(Mission.name), tostring(Mission.type), UTILS.SecondsToClock(Mission.Tstart, true), Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop, true) or "INF") - self:I(self.lid..text) + self:T(self.lid..text) return self end @@ -641,13 +632,20 @@ function AIRWING:SetNumberCAP(n) return self end ---- Set number of TANKER flights constantly in the air. +--- Set number of TANKER flights with Boom constantly in the air. -- @param #AIRWING self -- @param #number Nboom Number of flights. Default 1. +-- @return #AIRWING self +function AIRWING:SetNumberTankerBoom(Nboom) + self.nflightsTANKERboom=Nboom or 1 + return self +end + +--- Set number of TANKER flights with Probe constantly in the air. +-- @param #AIRWING self -- @param #number Nprobe Number of flights. Default 1. -- @return #AIRWING self -function AIRWING:SetNumberTANKER(Nboom, Nprobe) - self.nflightsTANKERboom=Nboom or 1 +function AIRWING:SetNumberTankerProbe(Nprobe) self.nflightsTANKERprobe=Nprobe or 1 return self end @@ -833,6 +831,7 @@ function AIRWING:onafterStatus(From, Event, To) ------------------ if self.verbose>=2 then local text=string.format("Missions Total=%d:", #self.missionqueue) + env.info("FF verbose "..self.verbose) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -1170,7 +1169,7 @@ function AIRWING:_GetNextMission() end end end - self:I(self.lid..string.format("Provided %d assets with payloads. Could not get payload for %d assets", #gotpayload, #remove)) + self:T(self.lid..string.format("Provided %d assets with payloads. Could not get payload for %d assets", #gotpayload, #remove)) -- Now remove assets for which we don't have a payload. for i=#assets,1,-1 do @@ -1263,7 +1262,7 @@ function AIRWING:CalculateAssetMissionScore(asset, Mission, includePayload) -- Intercepts need to be carried out quickly. We prefer spawned assets. if Mission.type==AUFTRAG.Type.INTERCEPT then if asset.spawned then - self:I("FF adding 25 to asset because it is spawned") + self:T(self.lid.."Adding 25 to asset because it is spawned") score=score+25 end end @@ -1337,7 +1336,7 @@ function AIRWING:_OptimizeAssetSelection(assets, Mission, includePayload) asset.dist=nil asset.score=nil end - self:I(self.lid..text) + self:T2(self.lid..text) end @@ -1421,6 +1420,7 @@ end -- @param Ops.Auftrag#AUFTRAG Mission The mission to be cancelled. function AIRWING:onafterMissionCancel(From, Event, To, Mission) + -- Info message. self:I(self.lid..string.format("Cancel mission %s", Mission.name)) if Mission:IsPlanned() or Mission:IsQueued() or Mission:IsRequested() then @@ -1465,7 +1465,7 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Debug text. local text=string.format("New asset %s with assignment %s and request assignment %s", asset.spawngroupname, tostring(asset.assignment), tostring(assignment)) - self:I(self.lid..text) + self:T3(self.lid..text) -- Get squadron. local squad=self:GetSquadron(asset.assignment) @@ -1479,7 +1479,7 @@ function AIRWING:onafterNewAsset(From, Event, To, asset, assignment) -- Debug text. local text=string.format("Adding asset to squadron %s: assignment=%s, type=%s, attribute=%s, nunits=%d %s", squad.name, assignment, asset.unittype, asset.attribute, nunits, tostring(squad.ngrouping)) - self:I(self.lid..text) + self:T(self.lid..text) -- Adjust number of elements in the group. if squad.ngrouping then @@ -1538,7 +1538,7 @@ end -- @param #AIRWING.SquadronAsset Asset The asset that returned. function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) -- Debug message. - self:I(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) + self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) -- Stop flightgroup. Asset.flightgroup:Stop() @@ -1607,8 +1607,12 @@ function AIRWING:onafterAssetSpawned(From, Event, To, group, asset, request) end if squadron.fuellow then - flightgroup:SetFuelCriticalThreshold(squadron.fuellow) + flightgroup:SetFuelLowThreshold(squadron.fuellow) end + + if squadron.fuellowRefuel then + flightgroup:SetFuelLowRefuel(squadron.fuellowRefuel) + end --- -- Mission @@ -2111,7 +2115,7 @@ function AIRWING:CanMission(Mission) local Npayloads=self:CountPayloadsInStock(Mission.type, unittypes, Mission.payloads) if Npayloads #Assets then - self:I(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) + self:T(self.lid..string.format("INFO: Not enough assets available! Got %d but need at least %d", #Assets, Mission.nassets)) Can=false end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index dc1911eda..e65d72d06 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -227,7 +227,7 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults - self:SetVerbosity(3) + self:SetVerbosity(0) self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() @@ -614,7 +614,7 @@ function FLIGHTGROUP:StartUncontrolled(delay) if self:IsAlive() then --TODO: check Alive==true and Alive==false ==> Activate first - self:I(self.lid.."Starting uncontrolled group") + self:T(self.lid.."Starting uncontrolled group") self.group:StartUncontrolled(delay) self.isUncontrolled=true else @@ -637,7 +637,7 @@ function FLIGHTGROUP:ClearToLand(Delay) else if self:IsHolding() then - self:I(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) + self:T(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) self.flaghold:Set(1) end @@ -729,7 +729,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local nMissions=self:CountRemainingMissison() -- Short info. - if self.verbose>0 then + if self.verbose>=1 then local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Curr=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s", fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, self.detectedunits:Count(), self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "unknown") @@ -737,7 +737,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end -- Element status. - if self.verbose>1 then + if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do local element=_element --#FLIGHTGROUP.Element @@ -771,7 +771,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Distance travelled --- - if self.verbose>1 and self:IsAlive() and self.position then + if self.verbose>=3 and self:IsAlive() and self.position then local time=timer.getAbsTime() @@ -933,7 +933,7 @@ function FLIGHTGROUP:OnEventBirth(EventData) end -- Set element to spawned state. - self:I(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) + self:T(self.lid..string.format("EVENT: Element %s born at airbase %s==> spawned", element.name, self.homebase and self.homebase:GetName() or "unknown")) self:ElementSpawned(element) end @@ -1364,7 +1364,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterSpawned(From, Event, To) - self:I(self.lid..string.format("Flight spawned")) + self:T(self.lid..string.format("Flight spawned")) -- TODO: general routine in opsgroup self.traveldist=0 @@ -1713,7 +1713,7 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) --- if self:IsAirborne() then - self:I(self.lid.."No waypoints left ==> CheckGroupDone") + self:T(self.lid.."No waypoints left ==> CheckGroupDone") self:_CheckGroupDone() end @@ -1786,25 +1786,25 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Send flight to destination. if destbase then - self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") + self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") self:__RTB(-3, destbase) elseif destzone then - self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!") + self:T(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTZ!") self:__RTZ(-3, destzone) else - self:I(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") + self:T(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") self:__Wait(-1) end else - self:I(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions)) + self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions)) self:__Wait(-1) end else - self:I(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission))) + self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission))) end else - self:I(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) + self:T(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) self:__UpdateRoute(-1) end end @@ -1912,7 +1912,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp -- Check if mission is already over! if not (mystatus==AUFTRAG.GroupStatus.DONE or mystatus==AUFTRAG.GroupStatus.CANCELLED) then local text=string.format("Canceling mission %s in state=%s", mission.name, mission.status) - env.info(text) + self:T(self.lid..text) self:MissionCancel(mission) end @@ -2159,6 +2159,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterRefueled(From, Event, To) + -- Debug message. local text=string.format("Flight group finished refuelling") self:I(self.lid..text) @@ -2558,28 +2559,30 @@ function FLIGHTGROUP:_InitGroup() self.refueltype=select(2, unit:IsRefuelable()) -- Debug info. - local text=string.format("Initialized Flight Group %s:\n", self.groupname) - text=text..string.format("AC type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) - text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) - text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) - text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) - text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) - text=text..string.format("AI = %s\n", tostring(self.ai)) - text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled())) - text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) - text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) - text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) - text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) - self:I(self.lid..text) + if self.verbose>=1 then + local text=string.format("Initialized Flight Group %s:\n", self.groupname) + text=text..string.format("AC type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) + text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) + text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) + text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) + text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) + text=text..string.format("AI = %s\n", tostring(self.ai)) + text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + text=text..string.format("Uncontrolled = %s\n", tostring(self:IsUncontrolled())) + text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) + text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) + text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) + text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) + self:I(self.lid..text) + end -- Init done. self.groupinitialized=true diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c4358afbf..91ef5f94f 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -486,8 +486,8 @@ end -- @param #OPSGROUP self -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3() - if self.group:IsAlive() then - self.group:GetVec3() + if self:IsAlive() then + return self.group:GetVec3() end return nil end @@ -1048,8 +1048,6 @@ function OPSGROUP:RemoveWaypoint(wpindex) if self.currentwp>=n then self.passedfinalwp=true end - - --env.info("FF passed final waypoint after remove! current wp = "..self.currentwp) self:_CheckGroupDone(1) @@ -1074,8 +1072,6 @@ function OPSGROUP:RemoveWaypoint(wpindex) else self.currentwp=self.currentwp-1 end - - --env.info("FF current waypoint after remove "..self.currentwp) end @@ -1868,7 +1864,7 @@ end function OPSGROUP:onbeforeMissionStart(From, Event, To, Mission) -- Debug info. - self:I(self.lid..string.format("Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s", tostring(Mission.name), self:GetState(), tostring(self:IsLateActivated()), tostring(self:IsUncontrolled()))) + self:T(self.lid..string.format("Starting mission %s, FSM=%s, LateActivated=%s, UnControlled=%s", tostring(Mission.name), self:GetState(), tostring(self:IsLateActivated()), tostring(self:IsUncontrolled()))) -- Delay for route to mission. Group needs to be activated and controlled. local delay=0 @@ -2075,30 +2071,22 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) AIRWING.UpdatePatrolPointMarker(Mission.patroldata) end - env.info("FF 000") - -- TACAN if Mission.tacan then - env.info("FF 100") - if self.tacanDefault then - env.info("FF 200") self:_SwitchTACAN(self.tacanDefault) else - env.info("FF 300") self:TurnOffTACAN() end local squadron=self.squadron --Ops.Squadron#SQUADRON if squadron then - env.info("FF 400") squadron:ReturnTacan(Mission.tacan.Channel) end local asset=Mission:GetAssetByName(self.groupname) if asset then - env.info("FF 500") asset.tacan=nil end end @@ -2302,7 +2290,7 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Debug info. local text=string.format("Group passed waypoint %s/%d ID=%d: final=%s detour=%s astar=%s", tostring(wpindex), #self.waypoints, Waypoint.uid, tostring(self.passedfinalwp), tostring(Waypoint.detour), tostring(Waypoint.astar)) - self:I(self.lid..text) + self:T(self.lid..text) end @@ -2604,7 +2592,7 @@ function OPSGROUP:_CheckGroupDone(delay) -- Start route at first waypoint. self:__UpdateRoute(-1, 1, speed) - self:I(self.lid..string.format("Passed final WP, #WP>1, adinfinitum=TRUE ==> Goto WP 1 at speed>0")) + self:T(self.lid..string.format("Passed final WP, #WP>1, adinfinitum=TRUE ==> Goto WP 1 at speed>0")) self.passedfinalwp=false @@ -2612,7 +2600,7 @@ function OPSGROUP:_CheckGroupDone(delay) -- No further waypoints. Command a full stop. self:__FullStop(-1) - self:I(self.lid..string.format("Passed final WP, #WP>1, adinfinitum=FALSE ==> Full Stop")) + self:T(self.lid..string.format("Passed final WP, #WP>1, adinfinitum=FALSE ==> Full Stop")) end elseif #self.waypoints==1 then @@ -2627,7 +2615,7 @@ function OPSGROUP:_CheckGroupDone(delay) if self.adinfinitum and dist>1000 then -- Note that dist>100 caused the same wp to be passed a lot of times. - self:I(self.lid..string.format("Passed final WP, #WP=1, adinfinitum=TRUE dist>1000 ==> Goto WP 1 at speed>0")) + self:T(self.lid..string.format("Passed final WP, #WP=1, adinfinitum=TRUE dist>1000 ==> Goto WP 1 at speed>0")) -- Get positive speed to first waypoint. local speed=self:GetSpeedToWaypoint(1) @@ -2639,7 +2627,7 @@ function OPSGROUP:_CheckGroupDone(delay) else - self:I(self.lid..string.format("Passed final WP, #WP=1, adinfinitum=FALSE or dist<1000 ==> Full Stop")) + self:T(self.lid..string.format("Passed final WP, #WP=1, adinfinitum=FALSE or dist<1000 ==> Full Stop")) self:__FullStop(-1) @@ -2661,7 +2649,7 @@ function OPSGROUP:_CheckGroupDone(delay) --- if #self.waypoints>0 then - self:I(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) + self:T(self.lid..string.format("NOT Passed final WP, #WP>0 ==> Update Route")) self:__UpdateRoute(-1) else self:E(self.lid..string.format("WARNING: No waypoints left! Commanding a Full Stop")) @@ -2815,7 +2803,7 @@ function OPSGROUP:InitWaypoints() end -- Debug info. - self:I(self.lid..string.format("Initializing %d waypoints", #self.waypoints)) + self:T(self.lid..string.format("Initializing %d waypoints", #self.waypoints)) -- Update route. if #self.waypoints>0 then @@ -2945,12 +2933,10 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Check special waypoints. if waypoint.astar then - env.info("FF removing Astar waypoint "..uid) opsgroup:RemoveWaypointByID(uid) elseif waypoint.detour then - env.info("FF removing Detour waypoint "..uid) opsgroup:RemoveWaypointByID(uid) -- Trigger event. @@ -3035,12 +3021,12 @@ function OPSGROUP:SwitchROE(roe) self.option.ROE=roe or ENUMS.ROE.ReturnFire if self:IsInUtero() then - self:I(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED", self.option.ROE)) + self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED", self.option.ROE)) else self.group:OptionROE(roe) - self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) + self:T(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) end @@ -3078,12 +3064,12 @@ function OPSGROUP:SwitchROT(rot) self.option.ROT=rot or ENUMS.ROT.PassiveDefense if self:IsInUtero() then - self:I(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) + self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) else self.group:OptionROT(self.option.ROT) - self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) + self:T(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.option.ROT)) end @@ -3127,7 +3113,7 @@ function OPSGROUP:SwitchAlarmstate(alarmstate) self.option.Alarm=alarmstate or 0 if self:IsInUtero() then - self:I(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED", self.option.Alarm)) + self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED", self.option.Alarm)) else if self.option.Alarm==0 then @@ -3142,7 +3128,7 @@ function OPSGROUP:SwitchAlarmstate(alarmstate) self.option.Alarm=0 end - self:I(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) + self:T(self.lid..string.format("Setting current Alarm State=%d (0=Auto, 1=Green, 2=Red)", self.option.Alarm)) end else @@ -3247,7 +3233,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) self.tacan.On=true if self:IsInUtero() then - self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s when GROUP is SPAWNED", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) + self:T(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s when GROUP is SPAWNED", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) else -- Activate beacon. @@ -3277,7 +3263,7 @@ function OPSGROUP:TurnOffTACAN() self.tacan.BeaconUnit:CommandDeactivateBeacon() end - self:I(self.lid..string.format("Switching TACAN OFF")) + self:T(self.lid..string.format("Switching TACAN OFF")) self.tacan.On=false end @@ -3343,7 +3329,7 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) if self:IsInUtero() then - self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) + self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) else -- Activate beacon. @@ -3371,7 +3357,7 @@ function OPSGROUP:TurnOffICLS() self.icls.BeaconUnit:CommandDeactivateICLS() end - self:I(self.lid..string.format("Switching ICLS OFF")) + self:T(self.lid..string.format("Switching ICLS OFF")) self.icls.On=false end @@ -3423,7 +3409,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) self.radio.On=true if self:IsInUtero() then - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) else -- Give command @@ -3458,7 +3444,7 @@ function OPSGROUP:TurnOffRadio() -- Radio is off. self.radio.On=false - self:I(self.lid..string.format("Switching radio OFF")) + self:T(self.lid..string.format("Switching radio OFF")) else self:E(self.lid.."ERROR: Radio can only be turned off for aircraft!") end diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 67f67419c..515bca3ea 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -27,6 +27,8 @@ -- @field #number ngrouping User defined number of units in the asset group. -- @field #table assets Squadron assets. -- @field #table missiontypes Capabilities (mission types and performances) of the squadron. +-- @field #number fuellow Low fuel threshold. +-- @field #boolean fuellowRefuel If `true`, flight tries to refuel at the nearest tanker. -- @field #number maintenancetime Time in seconds needed for maintenance of a returned flight. -- @field #number repairtime Time in seconds for each -- @field #string livery Livery of the squadron. @@ -87,7 +89,7 @@ SQUADRON = { --- SQUADRON class version. -- @field #string version -SQUADRON.version="0.1.0" +SQUADRON.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -315,7 +317,7 @@ function SQUADRON:AddMissionCapability(MissionTypes, Performance) end -- Debug info. - self:I(self.missiontypes) + self:T2(self.missiontypes) return self end @@ -400,6 +402,19 @@ function SQUADRON:SetFuelLowThreshold(LowFuel) return self end +--- Set if low fuel threshold is reached, flight tries to refuel at the neares tanker. +-- @param #SQUADRON self +-- @param #boolean switch If true or nil, flight goes for refuelling. If false, turn this off. +-- @return #SQUADRON self +function SQUADRON:SetFuelLowRefuel(switch) + if switch==false then + self.fuellowRefuel=false + else + self.fuellowRefuel=true + end + return self +end + --- Set airwing. -- @param #SQUADRON self -- @param Ops.AirWing#AIRWING Airwing The airwing. @@ -409,7 +424,6 @@ function SQUADRON:SetAirwing(Airwing) return self end - --- Add airwing asset to squadron. -- @param #SQUADRON self -- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset. @@ -439,7 +453,7 @@ end --- Get name of the squadron -- @param #SQUADRON self --- @return #sting Name of the squadron. +-- @return #string Name of the squadron. function SQUADRON:GetName() return self.name end @@ -536,7 +550,7 @@ function SQUADRON:FetchTacan() for channel,free in pairs(self.tacanChannel) do if free then - self:I(self.lid..string.format("Checking out Tacan channel %d", channel)) + self:T(self.lid..string.format("Checking out Tacan channel %d", channel)) self.tacanChannel[channel]=false return channel end @@ -549,7 +563,7 @@ end -- @param #SQUADRON self -- @param #number channel The channel that is available again. function SQUADRON:ReturnTacan(channel) - self:I(self.lid..string.format("Returning Tacan channel %d", channel)) + self:T(self.lid..string.format("Returning Tacan channel %d", channel)) self.tacanChannel[channel]=true end @@ -588,7 +602,7 @@ function SQUADRON:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting SQUADRON", self.name) - self:I(self.lid..text) + self:T(self.lid..text) -- Start the status monitoring. self:__Status(-1) @@ -628,7 +642,7 @@ function SQUADRON:onafterStatus(From, Event, To) end if not self:IsStopped() then - self:__Status(-30) + self:__Status(-60) end end @@ -745,13 +759,13 @@ function SQUADRON:CanMission(Mission) -- On duty?= if not self:IsOnDuty() then - self:I(self.lid..string.format("Squad in not OnDuty but in state %s. Cannot do mission %s with target %s", self:GetState(), Mission.name, Mission:GetTargetName())) + self:T(self.lid..string.format("Squad in not OnDuty but in state %s. Cannot do mission %s with target %s", self:GetState(), Mission.name, Mission:GetTargetName())) return false end -- Check mission type. WARNING: This assumes that all assets of the squad can do the same mission types! if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then - self:I(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) + self:T(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName())) return false end @@ -761,7 +775,7 @@ function SQUADRON:CanMission(Mission) if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then -- Correct refueling system. else - self:I(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) + self:T(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) return false end From a97d7abc243ca4029a71c64580cbe2ed9752db0b Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 Aug 2020 17:03:16 +0200 Subject: [PATCH 56/79] Range Ops --- Moose Development/Moose/Functional/Range.lua | 14 +++++++++++--- Moose Development/Moose/Functional/Warehouse.lua | 1 + Moose Development/Moose/Ops/OpsGroup.lua | 14 +++++++++----- Moose Development/Moose/Ops/Target.lua | 9 +++++++-- Moose Development/Moose/Wrapper/Positionable.lua | 8 ++++++-- 5 files changed, 34 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 6b292f2fd..6810783c8 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -93,6 +93,8 @@ -- @field Core.RadioQueue#RADIOQUEUE instructor Instructor radio queue. -- @field #number rangecontrolfreq Frequency on which the range control transmitts. -- @field Core.RadioQueue#RADIOQUEUE rangecontrol Range control radio queue. +-- @field #string rangecontrolrelayname Name of relay unit. +-- @field #string instructorrelayname Name of relay unit. -- @field #string soundpath Path inside miz file where the sound files are located. Default is "Range Soundfiles/". -- @extends Core.Fsm#FSM @@ -516,7 +518,7 @@ RANGE.MenuF10Root=nil --- Range script version. -- @field #string version -RANGE.version="2.2.2" +RANGE.version="2.2.3" --TODO list: --TODO: Verbosity level for messages. @@ -770,6 +772,7 @@ function RANGE:onafterStart() -- Set location where the messages are transmitted from. self.rangecontrol:SetSenderCoordinate(self.location) + self.rangecontrol:SetSenderUnitName(self.rangecontrolrelayname) -- Start range control radio queue. self.rangecontrol:Start(1, 0.1) @@ -794,6 +797,7 @@ function RANGE:onafterStart() -- Set location where the messages are transmitted from. self.instructor:SetSenderCoordinate(self.location) + self.instructor:SetSenderUnitName(self.instructorrelayname) -- Start instructor radio queue. self.instructor:Start(1, 0.1) @@ -1067,18 +1071,22 @@ end --- Enable range control and set frequency. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 256 MHz. +-- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetRangeControl(frequency) +function RANGE:SetRangeControl(frequency, relayunitname) self.rangecontrolfreq=frequency or 256 + self.rangecontrolrelayname=relayunitname return self end --- Enable instructor radio and set frequency. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 305 MHz. +-- @param #string relayunitname Name of the unit used for transmission. -- @return #RANGE self -function RANGE:SetInstructorRadio(frequency) +function RANGE:SetInstructorRadio(frequency, relayunitname) self.instructorfreq=frequency or 305 + self.instructorrelayname=relayunitname return self end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index eb851a28e..605fdd94a 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1552,6 +1552,7 @@ WAREHOUSE = { ClassName = "WAREHOUSE", Debug = false, + verbosity = 0, lid = nil, Report = true, warehouse = nil, diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 91ef5f94f..1976f178b 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -487,8 +487,9 @@ end -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3() if self:IsAlive() then - return self.group:GetVec3() - end + local vec3=self.group:GetVec3() + return vec3 + end return nil end @@ -520,7 +521,8 @@ end -- @return #number Velocity in m/s. function OPSGROUP:GetVelocity() if self:IsAlive()~=nil then - return self.group:GetVelocityMPS() + local vel=self.group:GetVelocityMPS() + return rel else self:E(self.lid.."WARNING: Group is not alive. Cannot get velocity!") end @@ -532,7 +534,8 @@ end -- @return #number Current heading of the group in degrees. function OPSGROUP:GetHeading() if self:IsAlive()~=nil then - return self.group:GetHeading() + local heading=self.group:GetHeading() + return heading else self:E(self.lid.."WARNING: Group is not alive. Cannot get heading!") end @@ -618,7 +621,8 @@ end function OPSGROUP:IsAlive() if self.group then - return self.group:IsAlive() + local alive=self.group:IsAlive() + return alive end return nil diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index a4e44a61f..0e8909566 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -593,8 +593,13 @@ function TARGET:GetTargetLife(Target) elseif Target.Type==TARGET.ObjectType.UNIT then - if Target.Object and Target.Object:IsAlive() then - return Target.Object:GetLife() + local unit=Target.Object --Wrapper.Unit#UNIT + + if unit and unit:IsAlive() then + + -- Note! According to the profiler, there is a big difference if we "return unit:GetLife()" or "local life=unit:GetLife(); return life"! + local life=unit:GetLife() + return life else return 0 end diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 5097935bb..53db3504b 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -375,8 +375,10 @@ function POSITIONABLE:GetCoordinate() -- Get the current position. local PositionableVec3 = self:GetVec3() + local coord=COORDINATE:NewFromVec3(PositionableVec3) + -- Return a new coordiante object. - return COORDINATE:NewFromVec3(PositionableVec3) + return coord end @@ -420,8 +422,10 @@ function POSITIONABLE:GetOffsetCoordinate(x,y,z) -- Translate offset vector from map origin to the unit: v=u+a. local v={x=a.x+u.x, y=a.y+u.y, z=a.z+u.z} + local coord=COORDINATE:NewFromVec3(v) + -- Return the offset coordinate. - return COORDINATE:NewFromVec3(v) + return coord end --- Returns a random @{DCS#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. From 637081ebddbcbf212cab5c82fc5fa4c199b3d4d7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 Aug 2020 22:52:18 +0200 Subject: [PATCH 57/79] Ops --- Moose Development/Moose/Core/Database.lua | 2 +- Moose Development/Moose/Core/Timer.lua | 34 +++++++++++-- .../Moose/Functional/Warehouse.lua | 32 +++++++----- Moose Development/Moose/Globals.lua | 1 + Moose Development/Moose/Ops/ArmyGroup.lua | 14 +++-- Moose Development/Moose/Ops/Auftrag.lua | 17 +++++-- Moose Development/Moose/Ops/FlightGroup.lua | 25 +++++---- Moose Development/Moose/Ops/NavyGroup.lua | 31 ++++++----- Moose Development/Moose/Ops/OpsGroup.lua | 51 ++++++------------- Moose Development/Moose/Wrapper/Airbase.lua | 11 +++- 10 files changed, 128 insertions(+), 90 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 430542f5e..504a861b4 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -136,7 +136,7 @@ function DATABASE:New() self:_RegisterClients() self:_RegisterStatics() --self:_RegisterPlayers() - self:_RegisterAirbases() + --self:_RegisterAirbases() self.UNITS_Position = 0 diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index cbf739fa3..72fe2c19d 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -100,6 +100,12 @@ TIMER = { lid = nil, } +--- Timer ID. +_TIMERID=0 + +--- Timer data base. +_TIMERDB={} + --- TIMER class version. -- @field #string version TIMER.version="0.1.0" @@ -124,8 +130,6 @@ function TIMER:New(Function, ...) -- Inherit BASE. local self=BASE:Inherit(self, BASE:New()) --#TIMER - - self.lid="TIMER | " -- Function to call. self.func=Function @@ -136,6 +140,18 @@ function TIMER:New(Function, ...) -- Number of function calls. self.ncalls=0 + -- Increase counter + _TIMERID=_TIMERID+1 + + -- Set UID. + self.uid=_TIMERID + + -- Log id. + self.lid=string.format("TIMER UID=%d | ", self.uid) + + -- Add to DB. + _TIMERDB[self.uid]=self + return self end @@ -151,7 +167,7 @@ function TIMER:Start(Tstart, dT, Duration) local Tnow=timer.getTime() -- Start time in sec. - self.Tstart=Tstart or Tnow + self.Tstart=Tstart and Tnow+Tstart or Tnow+0.001 -- one millisecond delay if Tstart=nil -- Set time interval. self.dT=dT @@ -162,10 +178,10 @@ function TIMER:Start(Tstart, dT, Duration) end -- Call DCS timer function. - self.tid=timer.scheduleFunction(TIMER._Function, self, self.Tstart) + self.tid=timer.scheduleFunction(self._Function, self, self.Tstart) -- Set log id. - self.lid=string.format("TIMER ID=%d | ", self.tid) + self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid) -- Debug info. self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop))) @@ -186,8 +202,14 @@ function TIMER:Stop(Delay) else if self.tid then + + -- Remove timer function. self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) timer.removeFunction(self.tid) + + -- Remove DB entry. + _TIMERDB[self.uid]=nil + end end @@ -210,6 +232,8 @@ end -- @return #number Time when the function is called again or `nil` if the timer is stopped. function TIMER:_Function(time) + --self:I(self.lid.."FF calling timer _Function") + -- Call function. self.func(unpack(self.para)) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 605fdd94a..940251cec 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3521,12 +3521,14 @@ function WAREHOUSE:_JobDone() --------------- -- Info on job. - local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n", self.alias, request.uid, request.warehouse.alias) - text=text..string.format("- %d of %d assets delivered. Casualties %d.", ncargodelivered, ncargotot, ncargodead) - if request.ntransport>0 then - text=text..string.format("\n- %d of %d transports returned home. Casualties %d.", ntransporthome, ntransporttot, ntransportdead) + if self.verbosity>=1 then + local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n", self.alias, request.uid, request.warehouse.alias) + text=text..string.format("- %d of %d assets delivered. Casualties %d.", ncargodelivered, ncargotot, ncargodead) + if request.ntransport>0 then + text=text..string.format("\n- %d of %d transports returned home. Casualties %d.", ntransporthome, ntransporttot, ntransportdead) + end + self:_InfoMessage(text, 20) end - self:_InfoMessage(text, 20) -- Mark request for deletion. table.insert(done, request) @@ -3575,13 +3577,13 @@ function WAREHOUSE:_JobDone() -- Debug text. local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s", group:GetName(), speed, tostring(onground), airbase, tostring(inspawnzone), tostring(ishome)) - self:I(self.lid..text) + self:T(self.lid..text) if ishome then -- Info message. local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.", self.alias, request.uid, group:GetName()) - self:_InfoMessage(text) + self:T(self.lid..text) -- Debug smoke. if self.Debug then @@ -4807,7 +4809,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) end -- Move asset from pending queue into new warehouse. - env.info("FF asset arrived in wh. adding in 60 sec") + self:T(self.lid.."Asset arrived at warehouse adding in 60 sec") warehouse:__AddAsset(60, group) end @@ -4822,8 +4824,10 @@ end function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Debug info - local text=string.format("Warehouse %s: All assets delivered to warehouse %s!", self.alias, request.warehouse.alias) - self:_InfoMessage(text, 5) + if self.verbosity>=1 then + local text=string.format("Warehouse %s: All assets delivered to warehouse %s!", self.alias, request.warehouse.alias) + self:_InfoMessage(text, 5) + end -- Make some noise :) if self.Debug then @@ -5163,12 +5167,12 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local assetitem=_asset --#WAREHOUSE.Assetitem -- Debug info. - self:I(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) + self:T(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) if assetitem.spawned then n=n+1 else - self:E(self.lid.."FF What?! This should not happen!") + self:T(self.lid.."FF What?! This should not happen!") end end @@ -8498,8 +8502,8 @@ end -- @param #number duration Message display duration in seconds. Default 20 sec. If duration is zero, no message is displayed. function WAREHOUSE:_InfoMessage(text, duration) duration=duration or 20 - if duration>0 then - MESSAGE:New(text, duration):ToCoalitionIf(self:GetCoalition(), self.Debug or self.Report) + if duration>0 and self.Debug or self.Report then + MESSAGE:New(text, duration):ToCoalition(self:GetCoalition()) end self:I(self.lid..text) end diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index bdde44b6d..d972cf38b 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -15,4 +15,5 @@ _SETTINGS:SetPlayerMenuOn() _DATABASE:_RegisterCargos() _DATABASE:_RegisterZones() +_DATABASE:_RegisterAirbases() diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f5e92a8ef..f5e169e38 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -113,9 +113,13 @@ function ARMYGROUP:New(GroupName) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) -- Start the status monitoring. - self:__CheckZone(-1) - self:__Status(-2) - self:__QueueUpdate(-3) + self:__Status(-1) + + -- Start check zone timer. + self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 5) + + -- Start queue update timer. + self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(3, 30) return self end @@ -292,7 +296,7 @@ end -- @param #string To To state. -- @param #ARMYGROUP.Element Element The group element. function ARMYGROUP:onafterElementSpawned(From, Event, To, Element) - self:I(self.lid..string.format("Element spawned %s", Element.name)) + self:T(self.lid..string.format("Element spawned %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) @@ -318,7 +322,7 @@ end -- @param #string Event Event. -- @param #string To To state. function ARMYGROUP:onafterSpawned(From, Event, To) - self:I(self.lid..string.format("Group spawned!")) + self:T(self.lid..string.format("Group spawned!")) if self.ai then diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 6ce63f05a..ca8ac0159 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -429,7 +429,7 @@ AUFTRAG.TargetType={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.3.1" +AUFTRAG.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1801,7 +1801,7 @@ end -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. function AUFTRAG:AddOpsGroup(OpsGroup) - self:I(self.lid..string.format("Adding Ops group %s", OpsGroup.groupname)) + self:T(self.lid..string.format("Adding Ops group %s", OpsGroup.groupname)) local groupdata={} --#AUFTRAG.GroupData groupdata.opsgroup=OpsGroup @@ -1818,7 +1818,7 @@ end -- @param #AUFTRAG self -- @param Ops.OpsGroup#OPSGROUP OpsGroup The OPSGROUP object. function AUFTRAG:DelOpsGroup(OpsGroup) - self:I(self.lid..string.format("Removing OPS group %s", OpsGroup and OpsGroup.groupname or "nil (ERROR)!")) + self:T(self.lid..string.format("Removing OPS group %s", OpsGroup and OpsGroup.groupname or "nil (ERROR)!")) if OpsGroup then @@ -2139,15 +2139,24 @@ function AUFTRAG:Evaluate() -- Mission had targets --- - -- Number of current targets is still >0 ==> Not everything was destroyed. + -- Check if failed. if self.type==AUFTRAG.Type.TROOPTRANSPORT then + -- Transported groups have to survive. if Ntargets0 then failed=true end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index e65d72d06..2cd3fc699 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -328,11 +328,13 @@ function FLIGHTGROUP:New(group) self:_InitGroup() -- Start the status monitoring. - self:__CheckZone(-1) - self:__Status(-2) - self:__QueueUpdate(-3) - - --self.Timer=SCHEDULER:New() + self:__Status(-1) + + -- Start queue update timer. + self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) + + -- Start check zone timer. + self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(3, 10) return self end @@ -356,7 +358,7 @@ end -- @param Ops.AirWing#AIRWING airwing The AIRWING object. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetAirwing(airwing) - self:I(self.lid..string.format("Add flight to AIRWING %s", airwing.alias)) + self:T(self.lid..string.format("Add flight to AIRWING %s", airwing.alias)) self.airwing=airwing return self end @@ -2409,6 +2411,9 @@ function FLIGHTGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.RemoveUnit) + + self.timerCheckZone:Stop() + self.timerQueueUpdate:Stop() self.CallScheduler:Clear() @@ -2437,7 +2442,7 @@ end -- @param Wrapper.Group#GROUP group Group object. -- @param #FLIGHTGROUP flightgroup Flight group object. function FLIGHTGROUP._ReachedHolding(group, flightgroup) - flightgroup:I(flightgroup.lid..string.format("Group reached holding point")) + flightgroup:T2(flightgroup.lid..string.format("Group reached holding point")) -- Trigger Holding event. flightgroup:__Holding(-1) @@ -2447,7 +2452,7 @@ end -- @param Wrapper.Group#GROUP group Group object. -- @param #FLIGHTGROUP flightgroup Flight group object. function FLIGHTGROUP._ClearedToLand(group, flightgroup) - flightgroup:I(flightgroup.lid..string.format("Group was cleared to land")) + flightgroup:T2(flightgroup.lid..string.format("Group was cleared to land")) -- Trigger Landing event. flightgroup:__Landing(-1) @@ -2457,7 +2462,7 @@ end -- @param Wrapper.Group#GROUP group Group object. -- @param #FLIGHTGROUP flightgroup Flight group object. function FLIGHTGROUP._FinishedRefuelling(group, flightgroup) - flightgroup:T(flightgroup.lid..string.format("Group finished refueling")) + flightgroup:T2(flightgroup.lid..string.format("Group finished refueling")) -- Trigger Holding event. flightgroup:__Refueled(-1) @@ -2633,7 +2638,7 @@ function FLIGHTGROUP:AddElementByName(unitname) local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) - self:I(self.lid..text) + self:T(self.lid..text) -- Add element to table. table.insert(self.elements, element) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 9312b19c2..a7eb20c31 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -154,10 +154,14 @@ function NAVYGROUP:New(GroupName) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) -- Start the status monitoring. - self:__CheckZone(-1) - self:__Status(-2) - self:__QueueUpdate(-3) - + self:__Status(-1) + + -- Start check zone timer. + self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 5) + + -- Start queue update timer. + self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(3, 60) + return self end @@ -505,7 +509,7 @@ end -- @param #string To To state. -- @param #NAVYGROUP.Element Element The group element. function NAVYGROUP:onafterElementSpawned(From, Event, To, Element) - self:I(self.lid..string.format("Element spawned %s", Element.name)) + self:T(self.lid..string.format("Element spawned %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED) @@ -531,7 +535,7 @@ end -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterSpawned(From, Event, To) - self:I(self.lid..string.format("Group spawned!")) + self:T(self.lid..string.format("Group spawned!")) if self.ai then @@ -652,7 +656,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) if #waypoints>1 then - self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Depth=%d m", + self:T(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Depth=%d m", self.currentwp, n, #self.waypoints, #waypoints-1, UTILS.MpsToKnots(self.speed), depth)) @@ -710,7 +714,7 @@ end -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterDetourReached(From, Event, To) - self:I(self.lid.."Group reached detour coordinate.") + self:T(self.lid.."Group reached detour coordinate.") end --- On after "TurnIntoWind" event. @@ -770,7 +774,8 @@ end -- @param #boolean Uturn Return to the place we came from. function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To) - env.info("FF Turn Into Wind Over!") + -- Debug message. + self:T2(self.lid.."Turn Into Wind Over!") self.intowind.Over=true self.intowind.Open=false @@ -779,10 +784,10 @@ function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To) self:RemoveWaypointByID(self.intowind.waypoint.uid) if self.intowind.Uturn then - env.info("FF Turn Into Wind Over Uturn!") + self:T(self.lid.."Turn Into Wind Over ==> Uturn!") self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true) else - env.info("FF Turn Into Wind Over Next WP!") + self:T(self.lid.."FF Turn Into Wind Over ==> Next WP!") local indx=self:GetWaypointIndexNext() local speed=self:GetWaypointSpeed(indx) self:__UpdateRoute(-1, indx, speed) @@ -1041,7 +1046,7 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up -- Check if a coordinate was given or at least a positionable. if not Coordinate:IsInstanceOf("COORDINATE") then if Coordinate:IsInstanceOf("POSITIONABLE") or Coordinate:IsInstanceOf("ZONE_BASE") then - self:I(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE. Trying to get coordinate") + self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE. Trying to get coordinate") Coordinate=Coordinate:GetCoordinate() else self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE!") @@ -1070,7 +1075,7 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up self:_AddWaypoint(waypoint, wpnumber) -- Debug info. - self:I(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d", wpnumber, waypoint.uid, Speed, self.currentwp, #self.waypoints)) + self:T(self.lid..string.format("Adding NAVAL waypoint index=%d uid=%d, speed=%.1f knots. Last waypoint passed was #%d. Total waypoints #%d", wpnumber, waypoint.uid, Speed, self.currentwp, #self.waypoints)) -- Update route. if Updateroute==nil or Updateroute==true then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1976f178b..98bf57796 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -45,6 +45,8 @@ -- @field #boolean respawning Group is being respawned. -- @field Core.Set#SET_ZONE checkzones Set of zones. -- @field Core.Set#SET_ZONE inzones Set of zones in which the group is currently in. +-- @field Core.Timer#TIMER timerCheckZone Timer for check zones. +-- @field Core.Timer#TIMER timerQueueUpdate Timer for queue updates. -- @field #boolean groupinitialized If true, group parameters were initialized. -- @field #boolean detectionOn If true, detected units of the group are analyzed. -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. @@ -315,7 +317,6 @@ function OPSGROUP:New(Group) self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("*", "QueueUpdate", "*") -- Update task and mission queues. self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. self:AddTransition("*", "Respawn", "*") -- Respawn group. @@ -335,7 +336,6 @@ function OPSGROUP:New(Group) self:AddTransition("*", "OutOfBombs", "*") -- Group is out of bombs. self:AddTransition("*", "OutOfMissiles", "*") -- Group is out of missiles. - self:AddTransition("*", "CheckZone", "*") -- Check if group enters/leaves a certain zone. self:AddTransition("*", "EnterZone", "*") -- Group entered a certain zone. self:AddTransition("*", "LeaveZone", "*") -- Group leaves a certain zone. @@ -1038,7 +1038,7 @@ function OPSGROUP:RemoveWaypoint(wpindex) local n=#self.waypoints -- Debug info. - self:I(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) + self:T(self.lid..string.format("Removing waypoint index %d, current wp index %d. N %d-->%d", wpindex, self.currentwp, N, n)) -- Waypoint was not reached yet. if wpindex > self.currentwp then @@ -1188,7 +1188,7 @@ function OPSGROUP:AddTask(task, clock, description, prio, duration) table.insert(self.taskqueue, newtask) -- Info. - self:I(self.lid..string.format("Adding SCHEDULED task %s starting at %s", newtask.description, UTILS.SecondsToClock(newtask.time, true))) + self:T(self.lid..string.format("Adding SCHEDULED task %s starting at %s", newtask.description, UTILS.SecondsToClock(newtask.time, true))) self:T3({newtask=newtask}) return newtask @@ -1475,7 +1475,7 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Debug message. local text=string.format("Task %s ID=%d execute", tostring(Task.description), Task.id) - self:I(self.lid..text) + self:T(self.lid..text) -- Cancel current task if there is any. if self.taskcurrent>0 then @@ -1585,7 +1585,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) -- Debug info. local text=string.format("Current task %s ID=%d cancelled (flag %s=%d)", Task.description, Task.id, Task.stopflag:GetName(), stopflag) - self:I(self.lid..text) + self:T(self.lid..text) -- Set stop flag. When the flag is true, the _TaskDone function is executed and calls :TaskDone() Task.stopflag:Set(1) @@ -1601,7 +1601,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) else -- Debug info. - self:I(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE", Task.description, Task.id)) + self:T(self.lid..string.format("TaskCancel: Setting task %s ID=%d to DONE", Task.description, Task.id)) -- Call task done function. self:TaskDone(Task) @@ -1645,7 +1645,7 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) -- Debug message. local text=string.format("Task done: %s ID=%d", Task.description, Task.id) - self:I(self.lid..text) + self:T(self.lid..text) -- No current task. if Task.id==self.taskcurrent then @@ -1703,7 +1703,7 @@ function OPSGROUP:AddMission(Mission) -- Info text. local text=string.format("Added %s mission %s starting at %s, stopping at %s", tostring(Mission.type), tostring(Mission.name), UTILS.SecondsToClock(Mission.Tstart, true), Mission.Tstop and UTILS.SecondsToClock(Mission.Tstop, true) or "INF") - self:I(self.lid..text) + self:T(self.lid..text) return self end @@ -2015,7 +2015,7 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) local Task=Mission:GetGroupWaypointTask(self) -- Debug info. - self:I(self.lid..string.format("Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + self:T(self.lid..string.format("Cancel current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) -- Cancelling the mission is actually cancelling the current task. -- Note that two things can happen. @@ -2053,7 +2053,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Debug info. local text=string.format("Mission %s DONE!", Mission.name) - self:I(self.lid..text) + self:T(self.lid..text) -- Set group status. Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.DONE) @@ -2196,10 +2196,9 @@ end --- On after "QueueUpdate" event. -- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function OPSGROUP:onafterQueueUpdate(From, Event, To) +function OPSGROUP:_QueueUpdate() + + --env.info(self.lid.."FF queueupdate T="..timer.getTime()) --- -- Mission @@ -2253,10 +2252,6 @@ function OPSGROUP:onafterQueueUpdate(From, Event, To) end - -- Update queue every ~5 sec. - if not self:IsStopped() then - self:__QueueUpdate(-5) - end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2427,22 +2422,6 @@ function OPSGROUP:onafterLeaveZone(From, Event, To, Zone) self.inzones:Remove(zonename, true) end ---- On after "CheckZone" event. --- @param #OPSGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function OPSGROUP:onafterCheckZone(From, Event, To) - - if self:IsAlive()==true then - self:_CheckInZones() - end - - if not self:IsStopped() then - self:__CheckZone(-10) - end -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2451,7 +2430,7 @@ end -- @param #OPSGROUP self function OPSGROUP:_CheckInZones() - if self.checkzones then + if self.checkzones and self:IsAlive() then local Ncheck=self.checkzones:Count() local Ninside=self.inzones:Count() diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 1c344c896..979368b0e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -17,6 +17,7 @@ -- @field #table CategoryName Names of airbase categories. -- @field #string AirbaseName Name of the airbase. -- @field #number AirbaseID Airbase ID. +-- @field Core.Zone#ZONE AirbaseZone Circular zone around the airbase with a radius of 2500 meters. For ships this is a ZONE_UNIT object. -- @field #number category Airbase category. -- @field #table descriptors DCS descriptors. -- @field #boolean isAirdrome Airbase is an airdrome. @@ -493,8 +494,14 @@ 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 ) + if self.isShip then + local unit=UNIT:FindByName(AirbaseName) + if unit then + self.AirbaseZone=ZONE_UNIT:New(AirbaseName, unit, 2500) + end + else + self.AirbaseZone=ZONE_RADIUS:New(AirbaseName, vec2, 2500) + end else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end From 9a91145f892a07a686cf8bc0e5d8e16482d8278e Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 Aug 2020 23:44:15 +0200 Subject: [PATCH 58/79] Small --- Moose Development/Moose/Ops/ArmyGroup.lua | 8 ++++---- Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/NavyGroup.lua | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index f5e169e38..51cc84188 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -115,11 +115,11 @@ function ARMYGROUP:New(GroupName) -- Start the status monitoring. self:__Status(-1) - -- Start check zone timer. - self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 5) - -- Start queue update timer. - self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(3, 30) + self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) + + -- Start check zone timer. + self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 30) return self end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index ca8ac0159..5f0335a76 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -319,7 +319,7 @@ AUFTRAG.Type={ FERRY="Ferry Flight", INTERCEPT="Intercept", ORBIT="Orbit", - GCCAP="Patrol", + GCCAP="Ground Controlled CAP", RECON="Recon", RECOVERYTANKER="Recovery Tanker", RESCUEHELO="Rescue Helo", diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index a7eb20c31..cee733b88 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -155,12 +155,12 @@ function NAVYGROUP:New(GroupName) -- Start the status monitoring. self:__Status(-1) + + -- Start queue update timer. + self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(2, 5) -- Start check zone timer. - self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 5) - - -- Start queue update timer. - self.timerQueueUpdate=TIMER:New(self._QueueUpdate, self):Start(3, 60) + self.timerCheckZone=TIMER:New(self._CheckInZones, self):Start(2, 60) return self end From 5ee35f3fbb13061674d905dca3d75d58dc387966 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 31 Aug 2020 12:50:50 +0200 Subject: [PATCH 59/79] Less output --- Moose Development/Moose/Core/Database.lua | 2 +- Moose Development/Moose/Functional/Range.lua | 29 ++++++++++++++++++- .../Moose/Functional/Warehouse.lua | 20 ++++++++----- Moose Development/Moose/Ops/FlightGroup.lua | 2 +- Moose Development/Moose/Ops/Target.lua | 6 ++-- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 6 files changed, 46 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 504a861b4..7babe2481 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1253,7 +1253,7 @@ end -- @param #DATABASE self -- @param Ops.FlightGroup#FLIGHTGROUP flightgroup function DATABASE:AddFlightGroup(flightgroup) - self:I({NewFlightGroup=flightgroup.groupname}) + self:T({NewFlightGroup=flightgroup.groupname}) self.FLIGHTGROUPS[flightgroup.groupname]=flightgroup end diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 6810783c8..0f4b164fe 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1916,8 +1916,35 @@ end -- @param #string To To state. function RANGE:onafterStatus(From, Event, To) + local fsmstate=self:GetState() + + local text=string.format("Range status: %s", fsmstate) + + if self.instructor then + local alive="N/A" + if self.instructorrelayname then + local relay=UNIT:FindByName(self.instructorrelayname) + if relay then + alive=tostring(relay:IsAlive()) + end + end + text=text..string.format(", Instructor %.3f MHz (Relay=%s alive=%s)", self.instructorfreq, tostring(self.instructorrelayname), alive) + end + + if self.rangecontrol then + local alive="N/A" + if self.rangecontrolrelayname then + local relay=UNIT:FindByName(self.rangecontrolrelayname) + if relay then + alive=tostring(relay:IsAlive()) + end + end + text=text..string.format(", Control %.3f MHz (Relay=%s alive=%s)", self.rangecontrolfreq, tostring(self.rangecontrolrelayname), alive) + end + + -- Check range status. - self:I(self.id..string.format("Range status: %s", self:GetState())) + self:I(self.id..text) -- Check player status. self:_CheckPlayers() diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 940251cec..bf027907c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4280,10 +4280,12 @@ end function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Info message. - local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n", self.alias, Request.uid, Request.warehouse.alias) - text=text..string.format("Requested %s assets of %s=%s.\n", tostring(Request.nasset), Request.assetdesc, Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and "Asset list" or Request.assetdescval) - text=text..string.format("Transports %s of type %s.", tostring(Request.ntransport), tostring(Request.transporttype)) - self:_InfoMessage(text, 5) + if self.verbosity>=1 then + local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n", self.alias, Request.uid, Request.warehouse.alias) + text=text..string.format("Requested %s assets of %s=%s.\n", tostring(Request.nasset), Request.assetdesc, Request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST and "Asset list" or Request.assetdescval) + text=text..string.format("Transports %s of type %s.", tostring(Request.ntransport), tostring(Request.transporttype)) + self:_InfoMessage(text, 5) + end ------------------------------------------------------------------------------------------------------------------------------------ -- Cargo assets. @@ -5156,7 +5158,7 @@ end -- @param #WAREHOUSE.Pendingitem request The request of the dead asset. function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local text=string.format("Asset %s from request id=%d was spawned!", asset.spawngroupname, request.uid) - self:I(self.lid..text) + self:T(self.lid..text) -- Sete asset state to spawned. asset.spawned=true @@ -6281,10 +6283,12 @@ function WAREHOUSE:_OnEventArrived(EventData) local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. -- Debug info. - local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec", group:GetName(), self.alias, dt) - self:_InfoMessage(text) - + if self.verbosity>=1 then + local text=string.format("Air asset group %s from warehouse %s arrived at its destination. Trigger Arrived event in %d sec", group:GetName(), self.alias, dt) + self:_InfoMessage(text) + end + -- Arrived event. self:__Arrived(dt, group) end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 2cd3fc699..448667244 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1177,7 +1177,7 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) else -- TODO: This can happen if spawned on deck of a carrier! - self:E(self.lid..string.format("Element spawned not in air but not on any parking spot.")) + self:T(self.lid..string.format("Element spawned not in air but not on any parking spot.")) self:__ElementParking(0.11, Element) end end diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 0e8909566..7f8cdd7cb 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -341,7 +341,7 @@ end -- @param #TARGET.Object Target Target object. function TARGET:onafterObjectDamaged(From, Event, To, Target) - self:I(self.lid..string.format("Object %s damaged", Target.Name)) + self:T(self.lid..string.format("Object %s damaged", Target.Name)) end @@ -353,7 +353,7 @@ end -- @param #TARGET.Object Target Target object. function TARGET:onafterObjectDestroyed(From, Event, To, Target) - self:I(self.lid..string.format("Object %s destroyed", Target.Name)) + self:T(self.lid..string.format("Object %s destroyed", Target.Name)) -- Set target status. Target.Status=TARGET.ObjectStatus.DEAD @@ -381,7 +381,7 @@ end -- @param #string To To state. function TARGET:onafterDamaged(From, Event, To) - self:I(self.lid..string.format("Target damaged")) + self:T(self.lid..string.format("Target damaged")) end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 979368b0e..693428891 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1384,7 +1384,7 @@ function AIRBASE:GetRunwayData(magvar, mark) runway.endpoint=c2 -- Debug info. - self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) + --self:I(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m i=%d j=%d", self:GetName(), runway.idx, runway.heading, runway.length, i, j)) -- Debug mark if mark then From eb86d59203e4340bfbfca87bf6d6675408a8a011 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 1 Sep 2020 11:42:45 +0200 Subject: [PATCH 60/79] Ops --- Moose Development/Moose/Core/Point.lua | 6 ++- Moose Development/Moose/Core/Timer.lua | 10 ++--- Moose Development/Moose/Ops/FlightGroup.lua | 48 +++++++++++++++++---- Moose Development/Moose/Ops/OpsGroup.lua | 16 ++++--- Moose Development/Moose/Wrapper/Group.lua | 15 ++++--- 5 files changed, 67 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 97f0fb280..ca1529950 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -561,7 +561,8 @@ do -- COORDINATE return self else --env.info("FF translate with NEW coordinate T="..timer.getTime()) - return COORDINATE:New(x, y, z) + local coord=COORDINATE:New(x, y, z) + return coord end end @@ -761,7 +762,8 @@ do -- COORDINATE -- Move the vector to start at the end of A. vec=UTILS.VecAdd(self, vec) - return self:New(vec.x,vec.y,vec.z) + local coord=COORDINATE:New(vec.x,vec.y,vec.z) + return coord end --- Return the 2D distance in meters between the target COORDINATE and the COORDINATE. diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 72fe2c19d..9b8a9200b 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -38,6 +38,8 @@ -- -- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. -- +-- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction). +-- -- # Construction -- -- A new TIMER is created by the @{#TIMER.New}(*func*, *...*) function @@ -104,7 +106,7 @@ TIMER = { _TIMERID=0 --- Timer data base. -_TIMERDB={} +--_TIMERDB={} --- TIMER class version. -- @field #string version @@ -150,7 +152,7 @@ function TIMER:New(Function, ...) self.lid=string.format("TIMER UID=%d | ", self.uid) -- Add to DB. - _TIMERDB[self.uid]=self + --_TIMERDB[self.uid]=self return self end @@ -208,7 +210,7 @@ function TIMER:Stop(Delay) timer.removeFunction(self.tid) -- Remove DB entry. - _TIMERDB[self.uid]=nil + --_TIMERDB[self.uid]=nil end @@ -232,8 +234,6 @@ end -- @return #number Time when the function is called again or `nil` if the timer is stopped. function TIMER:_Function(time) - --self:I(self.lid.."FF calling timer _Function") - -- Call function. self.func(unpack(self.para)) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 448667244..ca7afde95 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -227,7 +227,7 @@ function FLIGHTGROUP:New(group) self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) -- Defaults - self:SetVerbosity(0) + --self:SetVerbosity(0) self:SetFuelLowThreshold() self:SetFuelLowRTB() self:SetFuelCriticalThreshold() @@ -410,6 +410,25 @@ function FLIGHTGROUP:GetFlightControl() end +--- Set the homebase. +-- @param #FLIGHTGROUP self +-- @param Wrapper.Airbase#AIRBASE HomeAirbase The home airbase. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetHomebase(HomeAirbase) + self.homebase=HomeAirbase + return self +end + +--- Set the destination airbase. This is where the flight will go, when the final waypoint is reached. +-- @param #FLIGHTGROUP self +-- @param Wrapper.Airbase#AIRBASE DestinationAirbase The destination airbase. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetDestinationbase(DestinationAirbase) + self.destbase=DestinationAirbase + return self +end + + --- Set the AIRBOSS controlling this flight group. -- @param #FLIGHTGROUP self -- @param Ops.Airboss#AIRBOSS airboss The AIRBOSS object. @@ -1386,7 +1405,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) if self.option.ROT then self:SwitchROT(self.option.ROT) else - self:SwitchROE(ENUMS.ROT.PassiveDefense) + self:SwitchROT(ENUMS.ROT.PassiveDefense) end -- Turn TACAN beacon on. @@ -1529,6 +1548,7 @@ function FLIGHTGROUP:onafterLanding(From, Event, To) end + --- On after "Landed" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -1538,16 +1558,24 @@ end function FLIGHTGROUP:onafterLanded(From, Event, To, airbase) self:T(self.lid..string.format("Flight landed at %s", airbase and airbase:GetName() or "unknown place")) - if self:IsLandingAt() then - self:LandedAt() - else - if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then - -- Add flight to taxiinb queue. - self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB) - end + if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then + -- Add flight to taxiinb queue. + self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB) end + end +--- On after "LandedAt" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed. +function FLIGHTGROUP:onafterLandedAt(From, Event, To) + self:I(self.lid..string.format("Flight landed at")) +end + + --- On after "Arrived" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -2519,6 +2547,8 @@ function FLIGHTGROUP:_InitGroup() self.radio.Freq=self.template.frequency self.radio.Modu=self.template.modulation + self.radioDefault=UTILS.DeepCopy(self.radio) + --TODO callsign from template or getCallsign self.callsign.NumberSquad=self.template.units[1].callsign[1] self.callsign.NumberGroup=self.template.units[1].callsign[2] diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 98bf57796..1e2102f51 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -95,7 +95,7 @@ OPSGROUP = { ClassName = "OPSGROUP", Debug = false, - verbose = 0, + verbose = 3, lid = nil, groupname = nil, group = nil, @@ -2443,7 +2443,7 @@ function OPSGROUP:_CheckInZones() for inzonename, inzone in pairs(self.inzones:GetSet()) do -- Check if group is still inside the zone. - local isstillinzone=self.group:IsPartlyOrCompletelyInZone(inzone) + local isstillinzone=self.group:IsInZone(inzone) --:IsPartlyOrCompletelyInZone(inzone) -- If not, trigger, LeaveZone event. if not isstillinzone then @@ -2463,7 +2463,7 @@ function OPSGROUP:_CheckInZones() local checkzone=_checkzone --Core.Zone#ZONE -- Is group currtently in this check zone? - local isincheckzone=self.group:IsPartlyOrCompletelyInZone(checkzone) + local isincheckzone=self.group:IsInZone(checkzone) --:IsPartlyOrCompletelyInZone(checkzone) if isincheckzone and not self.inzones:_Find(checkzonename) then table.insert(enterzones, checkzone) @@ -3009,7 +3009,7 @@ function OPSGROUP:SwitchROE(roe) self.group:OptionROE(roe) - self:T(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) + self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) end @@ -3475,7 +3475,7 @@ function OPSGROUP:SwitchFormation(Formation) self.option.Formation=Formation -- Debug info. - self:I(self.lid..string.format("Switching formation to %d", self.formation)) + self:I(self.lid..string.format("Switching formation to %d", self.option.Formation)) end @@ -3771,7 +3771,11 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:Landed(airbase) + if self:IsLandingAt() then + self:LandedAt() + else + self:Landed(airbase) + end end elseif newstatus==OPSGROUP.ElementStatus.ARRIVED then diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 7ceb235af..34172a162 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -951,7 +951,8 @@ function GROUP:GetVec2() local Unit=self:GetUnit(1) if Unit then - return Unit:GetVec2() + local vec2=Unit:GetVec2() + return vec2 end end @@ -965,7 +966,8 @@ function GROUP:GetVec3() local unit=self:GetUnit(1) if unit then - return unit:GetVec3() + local vec3=unit:GetVec3() + return vec3 end self:E("ERROR: Cannot get Vec3 of group "..tostring(self.GroupName)) @@ -996,13 +998,11 @@ end -- @param Wrapper.Group#GROUP self -- @return Core.Point#COORDINATE The COORDINATE of the GROUP. function GROUP:GetCoordinate() - self:F2( self.PositionableName ) local FirstUnit = self:GetUnit(1) if FirstUnit then local FirstUnitCoordinate = FirstUnit:GetCoordinate() - self:T3(FirstUnitCoordinate) return FirstUnitCoordinate end @@ -1176,7 +1176,7 @@ do -- Is Zone methods --- Check if any unit of a group is inside a @{Zone}. -- @param #GROUP self -- @param Core.Zone#ZONE_BASE Zone The zone to test. --- @return #boolean Returns true if at least one unit is inside the zone or false if no unit is inside. +-- @return #boolean Returns `true` if *at least one unit* is inside the zone or `false` if *no* unit is inside. function GROUP:IsInZone( Zone ) if self:IsAlive() then @@ -1184,7 +1184,10 @@ function GROUP:IsInZone( Zone ) for UnitID, UnitData in pairs(self:GetUnits()) do local Unit = UnitData -- Wrapper.Unit#UNIT - if Zone:IsVec3InZone(Unit:GetVec3()) then + -- Get 2D vector. That's all we need for the zone check. + local vec2=Unit:GetVec2() + + if Zone:IsVec2InZone(vec2) then return true -- At least one unit is in the zone. That is enough. else -- This one is not but another could be. From 444cc43971cc2fe5e8fea476de01c8669dc6b4aa Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 2 Sep 2020 00:05:05 +0200 Subject: [PATCH 61/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 5 +- Moose Development/Moose/Ops/FlightGroup.lua | 14 ++- Moose Development/Moose/Ops/NavyGroup.lua | 5 ++ Moose Development/Moose/Ops/OpsGroup.lua | 95 ++++++++++++++++----- 4 files changed, 91 insertions(+), 28 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 51cc84188..83ef84b60 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -796,7 +796,10 @@ function ARMYGROUP:_InitGroup() -- Radio parameters from template. self.radio.On=false -- Radio is always OFF for ground. self.radio.Freq=133 - self.radio.Modu=radio.modulation.AM + self.radio.Modu=radio.modulation.AM + + -- Set default radio. + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) -- Set default formation from first waypoint. self.option.Formation=self:GetWaypoint(1).action diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ca7afde95..f786bf6e7 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2543,11 +2543,17 @@ function FLIGHTGROUP:_InitGroup() self.ammo=self:GetAmmoTot() -- Radio parameters from template. - self.radio.On=self.template.communication - self.radio.Freq=self.template.frequency - self.radio.Modu=self.template.modulation + self.radio.Freq=tonumber(self.template.frequency) + self.radio.Modu=tonumber(self.template.modulation) + local on=tostring(self.template.communication):lower() + if on=="true" then + self.radio.On=true + else + self.radio.On=false + end - self.radioDefault=UTILS.DeepCopy(self.radio) + -- Set default radio. + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) --TODO callsign from template or getCallsign self.callsign.NumberSquad=self.template.units[1].callsign[1] diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index cee733b88..bb5006272 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -96,6 +96,8 @@ function NAVYGROUP:New(GroupName) -- Defaults self:SetDetection() + self:SetDefaultROE() + self:SetDefaultAlarmstate() self:SetPatrolAdInfinitum(true) self:SetPathfinding(false) @@ -1135,6 +1137,9 @@ function NAVYGROUP:_InitGroup() self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 self.radio.Modu=tonumber(self.template.units[1].modulation) + -- Set default radio. + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) + -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" self.option.Formation=self.optionDefault.Formation diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1e2102f51..7a5917094 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -665,11 +665,18 @@ end --- Check if this group is currently "uncontrolled" and needs to be "started" to begin its route. -- @param #OPSGROUP self --- @return #boolean If this group uncontrolled. +-- @return #boolean If true, this group uncontrolled. function OPSGROUP:IsUncontrolled() return self.isUncontrolled end +--- Check if this group has passed its final waypoint. +-- @param #OPSGROUP self +-- @return #boolean If true, this group has passed the final waypoint. +function OPSGROUP:HasPassedFinalWaypoint() + return self.passedfinalwp +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2995,13 +3002,13 @@ end --- Set current ROE for the group. -- @param #OPSGROUP self --- @param #string roe ROE of group. Default is `ENUMS.ROE.ReturnFire`. +-- @param #string roe ROE of group. Default is value set in `SetDefaultROE` (usually `ENUMS.ROE.ReturnFire`). -- @return #OPSGROUP self function OPSGROUP:SwitchROE(roe) if self:IsAlive() or self:IsInUtero() then - self.option.ROE=roe or ENUMS.ROE.ReturnFire + self.option.ROE=roe or self.optionDefault.ROE if self:IsInUtero() then self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED", self.option.ROE)) @@ -3009,7 +3016,7 @@ function OPSGROUP:SwitchROE(roe) self.group:OptionROE(roe) - self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.option.ROE)) + self:I(self.lid..string.format("Setting current ROE=%d (%s)", self.option.ROE, self:_GetROEName(self.option.ROE))) end @@ -3020,6 +3027,24 @@ function OPSGROUP:SwitchROE(roe) return self end +--- Set current ROE for the group. +-- @param #OPSGROUP self +function OPSGROUP:_GetROEName(roe) + local name="unknown" + if roe==0 then + name="Weapon Free" + elseif roe==1 then + name="Open Fire/Weapon Free" + elseif roe==2 then + name="Open Fire" + elseif roe==3 then + name="Return Fire" + elseif roe==4 then + name="Weapon Hold" + end + return name +end + --- Get current ROE of the group. -- @param #OPSGROUP self -- @return #number Current ROE. @@ -3038,13 +3063,13 @@ end --- Set ROT for the group. -- @param #OPSGROUP self --- @param #string rot ROT of group. Default is `ENUMS.ROT.PassiveDefense`. +-- @param #string rot ROT of group. Default is value set in `:SetDefaultROT` (usually `ENUMS.ROT.PassiveDefense`). -- @return #OPSGROUP self function OPSGROUP:SwitchROT(rot) if self:IsAlive() or self:IsInUtero() then - self.option.ROT=rot or ENUMS.ROT.PassiveDefense + self.option.ROT=rot or self.optionDefault.ROT if self:IsInUtero() then self:T2(self.lid..string.format("Setting current ROT=%d when GROUP is SPAWNED", self.option.ROT)) @@ -3350,12 +3375,18 @@ end -- @param #OPSGROUP self -- @param #number Frequency Radio frequency in MHz. Default 251 MHz. -- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. +-- @param #boolean OffSwitch If true, radio is OFF by default. -- @return #OPSGROUP self -function OPSGROUP:SetDefaultRadio(Frequency, Modulation) +function OPSGROUP:SetDefaultRadio(Frequency, Modulation, OffSwitch) self.radioDefault={} self.radioDefault.Freq=Frequency or 251 self.radioDefault.Modu=Modulation or radio.modulation.AM + if OffSwitch then + self.radioDefault.On=false + else + self.radioDefault.On=true + end return self end @@ -3364,42 +3395,63 @@ end -- @param #OPSGROUP self -- @return #number Radio frequency in MHz or nil. -- @return #number Radio modulation or nil. +-- @return #boolean If true, the radio is on. Otherwise, radio is turned off. function OPSGROUP:GetRadio() - return self.radio.Freq, self.radio.Modu + return self.radio.Freq, self.radio.Modu, self.radio.On end --- Turn radio on or switch frequency/modulation. -- @param #OPSGROUP self --- @param #number Frequency Radio frequency in MHz. Default is 127.5 MHz. --- @param #number Modulation Radio modulation. Default `radio.Modulation.AM`. +-- @param #number Frequency Radio frequency in MHz. Default is value set in `SetDefaultRadio` (usually 251 MHz). +-- @param #number Modulation Radio modulation. Default is value set in `SetDefaultRadio` (usually `radio.Modulation.AM`). -- @return #OPSGROUP self function OPSGROUP:SwitchRadio(Frequency, Modulation) if self:IsAlive() or self:IsInUtero() then - Frequency=Frequency or 127.5 - Modulation=Modulation or radio.modulation.AM + Frequency=Frequency or self.radioDefault.Freq + Modulation=Modulation or self.radioDefault.Modu local group=self.group --Wrapper.Group#GROUP if self.isAircraft and not self.radio.On then group:SetOption(AI.Option.Air.id.SILENCE, false) end + + -- Backup last radio settings. + if self.radio then + self.radioLast=UTILS.DeepCopy(self.radio) + end - -- Set radio + -- Set current radio. self.radio.Freq=Frequency self.radio.Modu=Modulation self.radio.On=true - if self:IsInUtero() then - self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) - else + -- Only switch radio if different. + if self.radio.Freq~=self.radioLast.Freq or self.radio.Modu~=self.radioLast.Modu then - -- Give command - group:CommandSetFrequency(Frequency, Modulation) - - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + --[[ + local text=string.format("\nRadio Freq=%.3f %.3f", self.radio.Freq, self.radioLast.Freq) + text=text..string.format("\nRadio Modu=%d %d", self.radio.Modu, self.radioLast.Modu) + text=text..string.format("\nRadio OnOf=%s %s", tostring(self.radio.On), tostring(self.radioLast.On)) + text=text..string.format("\nRadio %s", tostring(self.radio==self.radioLast)) + self:I(self.lid..text) + ]] + if self:IsInUtero() then + self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + else + + -- Give command + group:CommandSetFrequency(Frequency, Modulation) + + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + + end + + else + self:T(self.lid.."INFO: Current radio not switched as freq/modulation did not change") end else @@ -3420,9 +3472,6 @@ function OPSGROUP:TurnOffRadio() -- Set group to be silient. self.group:SetOption(AI.Option.Air.id.SILENCE, true) - - --self.radio.Freq=nil - --self.radio.Modu=nil -- Radio is off. self.radio.On=false From 8b640912c3517533e92be4c5853a95f41aab8190 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 2 Sep 2020 22:54:54 +0200 Subject: [PATCH 62/79] Ops --- Moose Development/Moose/Ops/AirWing.lua | 2 +- Moose Development/Moose/Ops/ArmyGroup.lua | 25 +++---- Moose Development/Moose/Ops/Auftrag.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 20 ++---- Moose Development/Moose/Ops/NavyGroup.lua | 39 ++++++----- Moose Development/Moose/Ops/OpsGroup.lua | 74 +++++++++++++-------- Moose Development/Moose/Ops/Squadron.lua | 2 +- Moose Development/Moose/Ops/Target.lua | 9 +-- 8 files changed, 84 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 5ea8ffe5a..efe9161e2 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -204,7 +204,7 @@ function AIRWING:New(warehousename, airwingname) self:AddTransition("*", "FlightOnMission", "*") -- Flight was spawned with a mission. -- Defaults: - self:SetVerbosity(0) + --self:SetVerbosity(0) self.nflightsCAP=0 self.nflightsAWACS=0 self.nflightsTANKERboom=0 diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 83ef84b60..33b6d96ca 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -324,21 +324,18 @@ end function ARMYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) + -- TODO + self.traveldist=0 + self.traveltime=timer.getAbsTime() + self.position=self:GetCoordinate() + if self.ai then -- Set default ROE. - if self.option.ROE then - self:SwitchROE(self.option.ROE) - else - self:SwitchROE(ENUMS.ROE.ReturnFire) - end + self:SwitchROE(self.option.ROE) -- Set default Alarm State. - if self.option.Alarm then - self:SwitchAlarmstate(self.option.Alarm) - else - self:SwitchAlarmstate(ENUMS.AlarmState.Auto) - end + self:SwitchAlarmstate(self.option.Alarm) -- Turn TACAN beacon on. if self.tacan.On then @@ -346,8 +343,8 @@ function ARMYGROUP:onafterSpawned(From, Event, To) end -- Turn on the radio. - if self.radio.On then - self:SwitchRadio(self.radio.Freq, self.radio.Modu) + if self.radioLast then + self:SwitchRadio(self.radioLast.Freq, self.radioLst.Modu) end end @@ -789,10 +786,6 @@ function ARMYGROUP:_InitGroup() -- Group ammo. self.ammo=self:GetAmmoTot() - self.traveldist=0 - self.traveltime=timer.getAbsTime() - self.position=self:GetCoordinate() - -- Radio parameters from template. self.radio.On=false -- Radio is always OFF for ground. self.radio.Freq=133 diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 5f0335a76..e7d7933b0 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -483,7 +483,7 @@ function AUFTRAG:New(Type) self.status=AUFTRAG.Status.PLANNED -- Defaults - self:SetVerbosity(0) + --self:SetVerbosity(0) self:SetName() self:SetPriority() self:SetTime() diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f786bf6e7..6bf9da7e2 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1394,19 +1394,11 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE. - if self.option.ROE then - self:SwitchROE(self.option.ROE) - else - self:SwitchROE(ENUMS.ROE.ReturnFire) - end + -- Set ROE. + self:SwitchROE(self.option.ROE) - -- Set default ROT. - if self.option.ROT then - self:SwitchROT(self.option.ROT) - else - self:SwitchROT(ENUMS.ROT.PassiveDefense) - end + -- Set ROT. + self:SwitchROT(self.option.ROT) -- Turn TACAN beacon on. if self.tacan.On then @@ -1414,8 +1406,8 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) end -- Turn on the radio. - if self.radio.On then - self:SwitchRadio(self.radio.Freq, self.radio.Modu) + if self.radioLast then + self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) end -- TODO: make this input. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index bb5006272..7a12f07e5 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -539,21 +539,18 @@ end function NAVYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) + -- TODO + self.traveldist=0 + self.traveltime=timer.getAbsTime() + self.position=self:GetCoordinate() + if self.ai then -- Set default ROE. - if self.option.ROE then - self:SwitchROE(self.option.ROE) - else - self:SwitchROE(ENUMS.ROE.ReturnFire) - end + self:SwitchROE(self.option.ROE) -- Set default Alarm State. - if self.option.Alarm then - self:SwitchAlarmstate(self.option.Alarm) - else - self:SwitchAlarmstate(0) - end + self:SwitchAlarmstate(self.option.ROT) -- Turn TACAN beacon on. if self.tacan.On then @@ -566,8 +563,8 @@ function NAVYGROUP:onafterSpawned(From, Event, To) end -- Turn on the radio. - if self.radio.On then - self:SwitchRadio(self.radio.Freq, self.radio.Modu) + if self.radioLast then + self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) else self.radio.On=true -- Radio is always on for ships. If not set, it is default. end @@ -1128,10 +1125,6 @@ function NAVYGROUP:_InitGroup() -- Group ammo. self.ammo=self:GetAmmoTot() - self.traveldist=0 - self.traveltime=timer.getAbsTime() - self.position=self:GetCoordinate() - -- Radio parameters from template. self.radio.On=false -- Radio is always on for ships but we set it to false to check if it has been changed before spawn. self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 @@ -1230,7 +1223,10 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) end -- Current coordinate. - local coordinate=self:GetCoordinate():SetAltitude(offsetY, true) + --local coordinate=self:GetCoordinate():SetAltitude(offsetY, true) + + local vec3=self:GetVec3() + vec3.y=offsetY -- Current heading. local heading=self:GetHeading() @@ -1239,8 +1235,11 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) --coordinate=coordinate:Translate(500, heading, true) local function LoS(dist) - local checkcoord=coordinate:Translate(dist, heading, true) - return coordinate:IsLOS(checkcoord, offsetY) + --local checkcoord=coordinate:Translate(dist, heading, true) + --return coordinate:IsLOS(checkcoord, offsetY) + local checkvec3=UTILS.VecTranslate(vec3, dist, heading) + local los=land.isVisible(vec3, checkvec3) + return los end -- First check if everything is clear. @@ -1265,7 +1264,7 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local los=LoS(x) -- Debug message. - self:T(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) + self:I(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) if los and d<=eps then return x diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7a5917094..c3ea95bf4 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -95,7 +95,7 @@ OPSGROUP = { ClassName = "OPSGROUP", Debug = false, - verbose = 3, + verbose = 0, lid = nil, groupname = nil, group = nil, @@ -522,7 +522,7 @@ end function OPSGROUP:GetVelocity() if self:IsAlive()~=nil then local vel=self.group:GetVelocityMPS() - return rel + return vel else self:E(self.lid.."WARNING: Group is not alive. Cannot get velocity!") end @@ -2102,7 +2102,22 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) end end - -- TODO: reset mission specific parameters like radio, ROE etc. + -- TODO: reset mission specific parameters like radio, ROE etc. + if Mission.radio and self.radioLast then + self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) + end + + if Mission.optionROE then + self:SwitchROE() + end + + if Mission.optionROT then + self:SwitchROT() + end + + if Mission.optionAlarm then + self:SwitchAlarmstate() + end -- Check if group is done. self:_CheckGroupDone(1) @@ -3014,7 +3029,7 @@ function OPSGROUP:SwitchROE(roe) self:T2(self.lid..string.format("Setting current ROE=%d when GROUP is SPAWNED", self.option.ROE)) else - self.group:OptionROE(roe) + self.group:OptionROE(self.option.ROE) self:I(self.lid..string.format("Setting current ROE=%d (%s)", self.option.ROE, self:_GetROEName(self.option.ROE))) end @@ -3118,7 +3133,7 @@ function OPSGROUP:SwitchAlarmstate(alarmstate) if self:IsAlive() or self:IsInUtero() then - self.option.Alarm=alarmstate or 0 + self.option.Alarm=alarmstate or self.optionDefault.Alarm if self:IsInUtero() then self:T2(self.lid..string.format("Setting current Alarm State=%d when GROUP is SPAWNED", self.option.Alarm)) @@ -3407,10 +3422,19 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchRadio(Frequency, Modulation) - if self:IsAlive() or self:IsInUtero() then + Frequency=Frequency or self.radioDefault.Freq + Modulation=Modulation or self.radioDefault.Modu - Frequency=Frequency or self.radioDefault.Freq - Modulation=Modulation or self.radioDefault.Modu + + if self:IsInUtero() then + + -- Set current radio. + self.radioLast={} + self.radioLast.Freq=Frequency + self.radioLast.Modu=Modulation + + self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radioLast.Freq, UTILS.GetModulationName(self.radioLast.Modu))) + elseif self:IsAlive() then local group=self.group --Wrapper.Group#GROUP @@ -3423,35 +3447,29 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) self.radioLast=UTILS.DeepCopy(self.radio) end + -- Debug. + if false then + local text=string.format("\nRadio Freq=%.3f %.3f", self.radio.Freq, self.radioLast.Freq) + text=text..string.format("\nRadio Modu=%d %d", self.radio.Modu, self.radioLast.Modu) + text=text..string.format("\nRadio OnOf=%s %s", tostring(self.radio.On), tostring(self.radioLast.On)) + self:I(self.lid..text) + end + -- Set current radio. self.radio.Freq=Frequency - self.radio.Modu=Modulation + self.radio.Modu=Modulation self.radio.On=true -- Only switch radio if different. if self.radio.Freq~=self.radioLast.Freq or self.radio.Modu~=self.radioLast.Modu then - - --[[ - local text=string.format("\nRadio Freq=%.3f %.3f", self.radio.Freq, self.radioLast.Freq) - text=text..string.format("\nRadio Modu=%d %d", self.radio.Modu, self.radioLast.Modu) - text=text..string.format("\nRadio OnOf=%s %s", tostring(self.radio.On), tostring(self.radioLast.On)) - text=text..string.format("\nRadio %s", tostring(self.radio==self.radioLast)) - self:I(self.lid..text) - ]] - - if self:IsInUtero() then - self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) - else - -- Give command - group:CommandSetFrequency(Frequency, Modulation) - - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) - - end + -- Give command + group:CommandSetFrequency(Frequency, Modulation) + + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) else - self:T(self.lid.."INFO: Current radio not switched as freq/modulation did not change") + self:I(self.lid.."INFO: Current radio not switched as freq/modulation did not change") end else diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 515bca3ea..3748a0c7a 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -136,7 +136,7 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) self.Ngroups=Ngroups or 3 self:SetMissionRange() self:SetSkill(AI.Skill.GOOD) - self:SetVerbosity(0) + --self:SetVerbosity(0) -- Everyone can ORBIT. self:AddMissionCapability(AUFTRAG.Type.ORBIT) diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 7f8cdd7cb..32c1bcb78 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -201,14 +201,7 @@ function TARGET:New(TargetObject) -- @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 - + -- Start. self:__Start(-1) From cda16585418206628c60f1386914b843f86bacb0 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 4 Sep 2020 00:58:37 +0200 Subject: [PATCH 63/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 10 +++--- Moose Development/Moose/Ops/FlightGroup.lua | 9 ++++- Moose Development/Moose/Ops/NavyGroup.lua | 4 +-- Moose Development/Moose/Ops/OpsGroup.lua | 34 ++++++++++++++----- .../Moose/Wrapper/Positionable.lua | 3 +- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 33b6d96ca..d1c5f6d94 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -169,12 +169,14 @@ end -- @param #number Nshots Number of shots to fire. Default 3. -- @param #number WeaponType Type of weapon. Default auto. -- @param #number Prio Priority of the task. +-- @return Ops.OpsGroup#OPSGROUP.Task The task table. function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) - self:AddTask(DCStask, Clock, nil, Prio) + local task=self:AddTask(DCStask, Clock, nil, Prio) + return task end --- Add a *waypoint* task to fire at a given coordinate. @@ -262,7 +264,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- Info text. local text=string.format("%s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed), hdg, self.option.ROE, self.option.Alarm, self.option.Formation, nTaskTot, nMissions) + fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed or 0), hdg, self.option.ROE, self.option.Alarm, self.option.Formation, nTaskTot, nMissions) self:I(self.lid..text) else @@ -368,7 +370,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) n=n or self:GetWaypointIndexNext(self.adinfinitum) -- Debug info. - self:I(self.lid..string.format("FF Update route n=%d", n)) + --self:I(self.lid..string.format("FF Update route n=%d", n)) -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. self:_UpdateWaypointTasks(n) @@ -830,7 +832,7 @@ function ARMYGROUP:_InitGroup() -- Debug info. local text=string.format("Initialized Army Group %s:\n", self.groupname) - text=text..string.format("AC type = %s\n", self.actype) + text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 6bf9da7e2..ba503b469 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1410,6 +1410,13 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) end + -- Set callsign. + if self.callsignDefault then + self:SwitchCallsign(self.callsignDefault.NumberSquad, self.callsignDefault.NumberGroup) + else + self:SetDefaultCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) + end + -- TODO: make this input. self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true) self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) -- Does not seem to work. AI still used the after burner. @@ -2594,7 +2601,7 @@ function FLIGHTGROUP:_InitGroup() -- Debug info. if self.verbose>=1 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) - text=text..string.format("AC type = %s\n", self.actype) + text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 7a12f07e5..c4196feb7 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -1169,7 +1169,7 @@ function NAVYGROUP:_InitGroup() -- Debug info. local text=string.format("Initialized Navy Group %s:\n", self.groupname) - text=text..string.format("AC type = %s\n", self.actype) + text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) @@ -1177,7 +1177,7 @@ function NAVYGROUP:_InitGroup() text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive())) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) self:I(self.lid..text) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c3ea95bf4..edd0e496c 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -486,7 +486,7 @@ end -- @param #OPSGROUP self -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3() - if self:IsAlive() then + if self:IsAlive()~=nil then local vec3=self.group:GetVec3() return vec3 end @@ -2803,6 +2803,10 @@ function OPSGROUP:InitWaypoints() local coordinate=COORDINATE:New(wp.x, wp.alt, wp.y) local speedknots=UTILS.MpsToKnots(wp.speed) + if index==1 then + self.speed=wp.speed + end + self:AddWaypoint(coordinate, speedknots, index-1, nil, false) end @@ -3042,8 +3046,9 @@ function OPSGROUP:SwitchROE(roe) return self end ---- Set current ROE for the group. +--- Get name of ROE corresponding to the numerical value. -- @param #OPSGROUP self +-- @return #string Name of ROE. function OPSGROUP:_GetROEName(roe) local name="unknown" if roe==0 then @@ -3350,7 +3355,6 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) self.icls.BeaconUnit=unit self.icls.On=true - if self:IsInUtero() then self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) else @@ -3425,7 +3429,6 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) Frequency=Frequency or self.radioDefault.Freq Modulation=Modulation or self.radioDefault.Modu - if self:IsInUtero() then -- Set current radio. @@ -3433,7 +3436,8 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) self.radioLast.Freq=Frequency self.radioLast.Modu=Modulation - self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radioLast.Freq, UTILS.GetModulationName(self.radioLast.Modu))) + self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radioLast.Freq, UTILS.GetModulationName(self.radioLast.Modu))) + elseif self:IsAlive() then local group=self.group --Wrapper.Group#GROUP @@ -3570,16 +3574,28 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) - if self:IsAlive() then + CallsignName=CallsignName or self.callsignDefault.NumberSquad + CallsignNumber=CallsignNumber or self.callsignDefault.NumberGroup - self.callsign.NumberSquad=CallsignName or self.callsignDefault.NumberSquad - self.callsign.NumberGroup=CallsignNumber or self.callsignDefault.NumberGroup + if self:IsInUtero() then + + -- Set default callsign. We switch to this when group is spawned. + self:SetDefaultCallsign(CallsignName, CallsignNumber) + elseif self:IsAlive() then + + -- Set current callsign. + self.callsign.NumberSquad=CallsignName + self.callsign.NumberGroup=CallsignNumber + + -- Debug. self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsign.NumberSquad, self.callsign.NumberGroup)) - + -- Give command to change the callsign. self.group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) + else + --TODO: Error end return self diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index f7632d687..1b305e6e9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -814,7 +814,8 @@ end -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) - return UTILS.MpsToKnots(self:GetVelocityMPS()) + local velmps=self:GetVelocityMPS() + return UTILS.MpsToKnots(velmps) end --- Returns the Angle of Attack of a positionable. From 2e998dc3154adbfa642fa94e2506d0513dddd404 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 4 Sep 2020 17:20:40 +0200 Subject: [PATCH 64/79] Ops --- Moose Development/Moose/Ops/AirWing.lua | 12 +- Moose Development/Moose/Ops/ArmyGroup.lua | 23 +-- Moose Development/Moose/Ops/Auftrag.lua | 30 ++-- Moose Development/Moose/Ops/FlightGroup.lua | 15 +- Moose Development/Moose/Ops/NavyGroup.lua | 21 +-- Moose Development/Moose/Ops/OpsGroup.lua | 152 +++++++++++++++++--- Moose Development/Moose/Ops/Squadron.lua | 10 +- Moose Development/Moose/Ops/Target.lua | 2 +- 8 files changed, 199 insertions(+), 66 deletions(-) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index efe9161e2..9f1f6a9b3 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -44,7 +44,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\AirWing\AIRWING_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\AirWing\_Main.png) -- -- # The AIRWING Concept -- @@ -66,7 +66,7 @@ -- At this point the airwing does not have any assets (aircraft). In order to add these, one needs to first define SQUADRONS. -- -- VFA151=SQUADRON:New("F-14 Group", 8, "VFA-151 (Vigilantes)") --- VFA151:AddMissionCapability({AUFTRAG.Type.GCCAP, AUFTRAG.Type.INTERCEPT}) +-- VFA151:AddMissionCapability({AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) -- -- airwing:AddSquadron(VFA151) -- @@ -78,8 +78,8 @@ -- defined in the Mission Editor. -- -- -- F-14 payloads for CAP and INTERCEPT. Phoenix are first, sparrows are second choice. --- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCCAP}, 80) --- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCCAP}) +-- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-54C"), 2, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}, 80) +-- airwing:NewPayload(GROUP:FindByName("F-14 Payload AIM-7M"), 20, {AUFTRAG.Type.INTERCEPT, AUFTRAG.Type.GCICAP}) -- -- This will add two AIM-54C and 20 AIM-7M payloads. -- @@ -909,7 +909,7 @@ end -- @return #AIRWING self function AIRWING:CheckCAP() - local Ncap=self:CountMissionsInQueue({AUFTRAG.Type.GCCAP, AUFTRAG.Type.INTERCEPT}) + local Ncap=self:CountMissionsInQueue({AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT}) for i=1,self.nflightsCAP-Ncap do @@ -917,7 +917,7 @@ function AIRWING:CheckCAP() local altitude=patrol.altitude+1000*patrol.noccupied - local missionCAP=AUFTRAG:NewGCCAP(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) + local missionCAP=AUFTRAG:NewGCICAP(patrol.coord, altitude, patrol.speed, patrol.heading, patrol.leg) missionCAP.patroldata=patrol diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index d1c5f6d94..8bfe636fa 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -21,7 +21,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\ARMYGROUP\ArmyGroup_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\ArmyGroup\_Main.png) -- -- # The ARMYGROUP Concept -- @@ -147,7 +147,7 @@ end -- @return #ARMYGROUP self function ARMYGROUP:SetSpeedCruise(Speed) - self.speedCruise=Speed and UTILS.KnotsToKmph(Speed) or self.speedmax*0.7 + self.speedCruise=Speed and UTILS.KnotsToKmph(Speed) or self.speedMax*0.7 return self end @@ -264,7 +264,7 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- Info text. local text=string.format("%s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speed or 0), hdg, self.option.ROE, self.option.Alarm, self.option.Formation, nTaskTot, nMissions) + fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speedWp or 0), hdg, self.option.ROE, self.option.Alarm, self.option.Formation, nTaskTot, nMissions) self:I(self.lid..text) else @@ -411,7 +411,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) self.option.Formation=wp.action -- Current set speed in m/s. - self.speed=wp.speed + self.speedWp=wp.speed else @@ -457,14 +457,14 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Current waypoint. - local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speed), self.option.Formation) + local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp), self.option.Formation) table.insert(waypoints, 1, current) table.insert(waypoints, 1, current) -- Seems to be better to add this twice. Otherwise, the passing waypoint functions is triggered to early! if #waypoints>2 then self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Formation=%s", - self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speed), tostring(self.option.Formation))) + self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speedWp), tostring(self.option.Formation))) -- Route group to all defined waypoints remaining. self:Route(waypoints) @@ -595,6 +595,11 @@ function ARMYGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.RemoveUnit) + -- Stop check timers. + self.timerCheckZone:Stop() + self.timerQueueUpdate:Stop() + + -- Stop FSM scheduler. self.CallScheduler:Clear() self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") @@ -780,10 +785,10 @@ function ARMYGROUP:_InitGroup() self.isUncontrolled=false -- Max speed in km/h. - self.speedmax=self.group:GetSpeedMax() + self.speedMax=self.group:GetSpeedMax() -- Cruise speed in km/h - self.speedCruise=self.speedmax*0.7 + self.speedCruise=self.speedMax*0.7 -- Group ammo. self.ammo=self:GetAmmoTot() @@ -833,7 +838,7 @@ function ARMYGROUP:_InitGroup() -- Debug info. local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index e7d7933b0..5339dd03f 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -123,7 +123,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\CarrierAirWing\AUFTRAG_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\Auftrag\_Main.png) -- -- # The AUFTRAG Concept -- @@ -190,9 +190,9 @@ -- -- An orbit mission can be created with the @{#AUFTRAG.NewORBIT}() function. -- --- ## GCCAP +-- ## GCICAP -- --- An patrol mission can be created with the @{#AUFTRAG.NewGCCAP}() function. +-- An patrol mission can be created with the @{#AUFTRAG.NewGCICAP}() function. -- -- ## RECON -- @@ -296,7 +296,7 @@ _AUFTRAGSNR=0 -- @field #string FERRY Ferry flight mission. -- @field #string INTERCEPT Intercept mission. -- @field #string ORBIT Orbit mission. --- @field #string GCCAP Similar to CAP but no auto engage targets. +-- @field #string GCICAP Similar to CAP but no auto engage targets. -- @field #string RECON Recon mission. -- @field #string RECOVERYTANKER Recovery tanker mission. Not implemented yet. -- @field #string RESCUEHELO Rescue helo. @@ -319,7 +319,7 @@ AUFTRAG.Type={ FERRY="Ferry Flight", INTERCEPT="Intercept", ORBIT="Orbit", - GCCAP="Ground Controlled CAP", + GCICAP="Ground Controlled CAP", RECON="Recon", RECOVERYTANKER="Recovery Tanker", RESCUEHELO="Rescue Helo", @@ -641,7 +641,7 @@ function AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) return mission end ---- Create a Ground Controlled CAP (GCCAP) mission. Flights with this task are considered for A2A INTERCEPT missions by the CHIEF class. They will perform a compat air patrol but not engage by +--- Create a Ground Controlled CAP (GCICAP) mission. Flights with this task are considered for A2A INTERCEPT missions by the CHIEF class. They will perform a compat air patrol but not engage by -- themselfs. They wait for the CHIEF to tell them whom to engage. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. @@ -650,13 +650,13 @@ end -- @param #number Heading Heading of race-track pattern in degrees. Default random in [0, 360) degrees. -- @param #number Leg Length of race-track in NM. Default 10 NM. -- @return #AUFTRAG self -function AUFTRAG:NewGCCAP(Coordinate, Altitude, Speed, Heading, Leg) +function AUFTRAG:NewGCICAP(Coordinate, Altitude, Speed, Heading, Leg) -- Create ORBIT first. local mission=AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) - -- Mission type GCCAP. - mission.type=AUFTRAG.Type.GCCAP + -- Mission type GCICAP. + mission.type=AUFTRAG.Type.GCICAP mission:_SetLogID() @@ -1341,8 +1341,8 @@ function AUFTRAG:NewAUTO(EngageGroup) mission=AUFTRAG:NewFACA(Target,Designation,DataLink,Frequency,Modulation) elseif auftrag==AUFTRAG.Type.FERRY then -- Not implemented yet. - elseif auftrag==AUFTRAG.Type.GCCAP then - mission=AUFTRAG:NewGCCAP(Coordinate,Altitude,Speed,Heading,Leg) + elseif auftrag==AUFTRAG.Type.GCICAP then + mission=AUFTRAG:NewGCICAP(Coordinate,Altitude,Speed,Heading,Leg) elseif auftrag==AUFTRAG.Type.INTERCEPT then mission=AUFTRAG:NewINTERCEPT(Target) elseif auftrag==AUFTRAG.Type.ORBIT then @@ -3030,7 +3030,7 @@ function AUFTRAG:GetAssetByName(Name) return nil end ---- Count alive flight groups assigned for this mission. +--- Count alive ops groups assigned for this mission. -- @param #AUFTRAG self -- @return #number Number of alive flight groups. function AUFTRAG:CountOpsGroups() @@ -3262,10 +3262,10 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) -- Done below as also other mission types use the orbit task. - elseif self.type==AUFTRAG.Type.GCCAP then + elseif self.type==AUFTRAG.Type.GCICAP then -------------------- - -- GCCAP Mission -- + -- GCICAP Mission -- -------------------- -- Done below as also other mission types use the orbit task. @@ -3369,7 +3369,7 @@ function AUFTRAG:GetDCSMissionTask(TaskControllable) if self.type==AUFTRAG.Type.ORBIT or self.type==AUFTRAG.Type.CAP or self.type==AUFTRAG.Type.CAS or - self.type==AUFTRAG.Type.GCCAP or + self.type==AUFTRAG.Type.GCICAP or self.type==AUFTRAG.Type.AWACS or self.type==AUFTRAG.Type.TANKER then diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ba503b469..38de11853 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -53,7 +53,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\FlightGroup\FLIGHTGROUP_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\FlightGroup\_Main.png) -- -- # The FLIGHTGROUP Concept -- @@ -116,7 +116,7 @@ FLIGHTGROUP = { homezone = nil, destzone = nil, actype = nil, - speedmax = nil, + speedMax = nil, rangemax = nil, ceiling = nil, fuellow = false, @@ -2406,7 +2406,7 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) end end ---- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. +--- On after "Stop" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -2439,11 +2439,14 @@ function FLIGHTGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.RemoveUnit) + -- Stop check timers. self.timerCheckZone:Stop() self.timerQueueUpdate:Stop() + -- Stop FSM scheduler. self.CallScheduler:Clear() + -- Remove flight from data base. _DATABASE.FLIGHTGROUPS[self.groupname]=nil self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") @@ -2530,13 +2533,13 @@ function FLIGHTGROUP:_InitGroup() self.isLateActivated=self.template.lateActivation -- Max speed in km/h. - self.speedmax=group:GetSpeedMax() + self.speedMax=group:GetSpeedMax() -- Cruise speed limit 350 kts for fixed and 80 knots for rotary wings. local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) -- Cruise speed: 70% of max speed but within limit. - self.speedCruise=math.min(self.speedmax*0.7, speedCruiseLimit) + self.speedCruise=math.min(self.speedMax*0.7, speedCruiseLimit) -- Group ammo. self.ammo=self:GetAmmoTot() @@ -2602,7 +2605,7 @@ function FLIGHTGROUP:_InitGroup() if self.verbose>=1 then local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Range max = %.1f km\n", self.rangemax/1000) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index c4196feb7..711b5e223 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -29,7 +29,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\NAVYGROUP\NavyGroup_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\NavyGroup\_Main.png) -- -- # The NAVYGROUP Concept -- @@ -462,7 +462,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) local turning=tostring(self:IsTurning()) local alt=self.position.y local speed=UTILS.MpsToKnots(self.velocity) - local speedExpected=UTILS.MpsToKnots(self.speed or 0) + local speedExpected=UTILS.MpsToKnots(self.speedWp or 0) local wpidxCurr=self.currentwp local wpuidCurr=0 @@ -628,7 +628,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) end -- Current set speed in m/s. - self.speed=wp.speed + self.speedWp=wp.speed -- Current set depth. depth=wp.alt @@ -649,14 +649,14 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) end -- Current waypoint. - local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speed), depth) + local current=self:GetCoordinate():WaypointNaval(UTILS.MpsToKmph(self.speedWp), depth) table.insert(waypoints, 1, current) if #waypoints>1 then self:T(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Depth=%d m", - self.currentwp, n, #self.waypoints, #waypoints-1, UTILS.MpsToKnots(self.speed), depth)) + self.currentwp, n, #self.waypoints, #waypoints-1, UTILS.MpsToKnots(self.speedWp), depth)) -- Route group to all defined waypoints remaining. @@ -935,6 +935,11 @@ function NAVYGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.RemoveUnit) + -- Stop check timers. + self.timerCheckZone:Stop() + self.timerQueueUpdate:Stop() + + -- Stop FSM scheduler. self.CallScheduler:Clear() self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") @@ -1117,10 +1122,10 @@ function NAVYGROUP:_InitGroup() self.isUncontrolled=false -- Max speed in km/h. - self.speedmax=self.group:GetSpeedMax() + self.speedMax=self.group:GetSpeedMax() -- Cruise speed: 70% of max speed. - self.speedCruise=self.speedmax*0.7 + self.speedCruise=self.speedMax*0.7 -- Group ammo. self.ammo=self:GetAmmoTot() @@ -1170,7 +1175,7 @@ function NAVYGROUP:_InitGroup() -- Debug info. local text=string.format("Initialized Navy Group %s:\n", self.groupname) text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedmax)) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Waypoints = %d\n", #self.waypoints) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index edd0e496c..91d0168ba 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -38,8 +38,9 @@ -- @field #number currentmission The ID (auftragsnummer) of the currently assigned AUFTRAG. -- @field Core.Set#SET_UNIT detectedunits Set of detected units. -- @field #string attribute Generalized attribute. --- @field #number speedmax Max speed in km/h. +-- @field #number speedMax Max speed in km/h. -- @field #number speedCruise Cruising speed in km/h. +-- @field #number speedWp Speed to the next waypoint in km/h. -- @field #boolean passedfinalwp Group has passed the final waypoint. -- @field #number wpcounter Running number counting waypoints. -- @field #boolean respawning Group is being respawned. @@ -81,7 +82,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPSGROUP\OpsGroup_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\OpsGroup\_Main.png) -- -- # The OPSGROUP Concept -- @@ -427,7 +428,7 @@ end -- @param #OPSGROUP self -- @return #number Cruise speed (>0) in knots. function OPSGROUP:GetSpeedCruise() - return UTILS.KmphToKnots(self.speedCruise or self.speedmax*0.7) + return UTILS.KmphToKnots(self.speedCruise or self.speedMax*0.7) end --- Set detection on or off. @@ -482,13 +483,81 @@ function OPSGROUP:GetName() return self.groupname end +--- Get DCS GROUP object. +-- @param #OPSGROUP self +-- @return DCS#Group DCS group object. +function OPSGROUP:GetDCSGroup() + local DCSGroup=Group.getByName(self.groupname) + return DCSGroup +end + +--- Get DCS GROUP object. +-- @param #OPSGROUP self +-- @return DCS#Group DCS group object. +function OPSGROUP:GetUnit(UnitNumber) + local DCSGroup=Group.getByName(self.groupname) + return DCSGroup +end + +--- Get DCS GROUP object. +-- @param #OPSGROUP self +-- @param #number UnitNumber Number of the unit in the group. Default first unit. +-- @return DCS#Unit DCS group object. +function OPSGROUP:GetDCSUnit(UnitNumber) + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + local unit=DCSGroup:getUnit(UnitNumber or 1) + return unit + end + + return nil +end + +--- Despawn group. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:DespawnGroup() + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + DCSGroup:destroy() + end + + return self +end + +--- Despawn a unit. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:DespawnUnit(UnitName) + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + DCSGroup:destroy() + end + + return self +end + + --- Get current 3D vector of the group. -- @param #OPSGROUP self -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3() - if self:IsAlive()~=nil then - local vec3=self.group:GetVec3() - return vec3 + if self:IsExist() then + + local unit=self:GetDCSUnit() + + if unit then + local vec3=unit:getPoint() + + return vec3 + end + end return nil end @@ -520,11 +589,21 @@ end -- @param #OPSGROUP self -- @return #number Velocity in m/s. function OPSGROUP:GetVelocity() - if self:IsAlive()~=nil then - local vel=self.group:GetVelocityMPS() - return vel + if self:IsExist() then + + local unit=self:GetDCSUnit(1) + + if unit then + + local velvec3=unit:getVelocity() + + local vel=UTILS.VecNorm(velvec3) + + return vel + + end else - self:E(self.lid.."WARNING: Group is not alive. Cannot get velocity!") + self:E(self.lid.."WARNING: Group does not exist. Cannot get velocity!") end return nil end @@ -533,12 +612,30 @@ end -- @param #OPSGROUP self -- @return #number Current heading of the group in degrees. function OPSGROUP:GetHeading() - if self:IsAlive()~=nil then - local heading=self.group:GetHeading() - return heading + + if self:IsExist() then + + local unit=self:GetDCSUnit() + + if unit then + + local pos=unit:getPosition() + + local heading=math.atan2(pos.x.z, pos.x.x) + + if heading<0 then + heading=heading+ 2*math.pi + end + + heading=math.deg(heading) + + return heading + end + else - self:E(self.lid.."WARNING: Group is not alive. Cannot get heading!") + self:E(self.lid.."WARNING: Group does not exist. Cannot get heading!") end + return nil end @@ -615,6 +712,29 @@ function OPSGROUP:SelfDestruction(Delay, ExplosionPower) end + +--- Check if group is exists. +-- @param #OPSGROUP self +-- @return #boolean If true, the group exists or false if the group does not exist. If nil, the DCS group could not be found. +function OPSGROUP:IsExist() + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + local exists=DCSGroup:isExist() + return exists + end + + return nil +end + +--- Check if group is activated. +-- @param #OPSGROUP self +-- @return #boolean If true, the group exists or false if the group does not exist. If nil, the DCS group could not be found. +function OPSGROUP:IsActive() + +end + --- Check if group is alive. -- @param #OPSGROUP self -- @return #boolean *true* if group is exists and is activated, *false* if group is exist but is NOT activated. *nil* otherwise, e.g. the GROUP object is *nil* or the group is not spawned yet. @@ -1001,7 +1121,7 @@ function OPSGROUP:GetExpectedSpeed() if self:IsHolding() then return 0 else - return self.speed or 0 + return self.speedWp or 0 end end @@ -2804,7 +2924,7 @@ function OPSGROUP:InitWaypoints() local speedknots=UTILS.MpsToKnots(wp.speed) if index==1 then - self.speed=wp.speed + self.speedWp=wp.speed end self:AddWaypoint(coordinate, speedknots, index-1, nil, false) diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 3748a0c7a..a711f754c 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -52,7 +52,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\Squadron\SQUADRON_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\Squadron\_Main.png) -- -- # The SQUADRON Concept -- @@ -838,12 +838,12 @@ function SQUADRON:RecruitAssets(Mission, Npayloads) -- Asset is already on a mission. --- - -- Check if this asset is currently on a GCCAP mission (STARTED or EXECUTING). - if self.airwing:IsAssetOnMission(asset, AUFTRAG.Type.GCCAP) and Mission.type==AUFTRAG.Type.INTERCEPT then + -- Check if this asset is currently on a GCICAP mission (STARTED or EXECUTING). + if self.airwing:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and Mission.type==AUFTRAG.Type.INTERCEPT then -- Check if the payload of this asset is compatible with the mission. - -- Note: we do not check the payload as an asset that is on a GCCAP mission should be able to do an INTERCEPT as well! - self:I(self.lid.."Adding asset on GCCAP mission for an INTERCEPT mission") + -- Note: we do not check the payload as an asset that is on a GCICAP mission should be able to do an INTERCEPT as well! + self:I(self.lid.."Adding asset on GCICAP mission for an INTERCEPT mission") table.insert(assets, asset) end diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 32c1bcb78..233558e8b 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -32,7 +32,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\WingCommander\TARGET_Main.jpg) +-- ![Banner Image](..\Presentations\OPS\Target\_Main.pngs) -- -- # The TARGET Concept -- From 5ad30277ba587abd5d96f7b28fdc2d8cf93db034 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 5 Sep 2020 00:56:44 +0200 Subject: [PATCH 65/79] Ops --- Moose Development/Moose/Core/Base.lua | 16 +++ Moose Development/Moose/Ops/AirWing.lua | 12 +- Moose Development/Moose/Ops/Auftrag.lua | 19 ++- Moose Development/Moose/Ops/FlightGroup.lua | 40 ++---- Moose Development/Moose/Ops/OpsGroup.lua | 144 ++++++++++++++++++-- Moose Development/Moose/Ops/Squadron.lua | 28 ++-- 6 files changed, 198 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index bdedec8a1..854252cbc 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -717,6 +717,22 @@ function BASE:CreateEventCrash( EventTime, Initiator ) world.onEvent( Event ) end +--- Creation of a Crash Event. +-- @param #BASE self +-- @param DCS#Time EventTime The time stamp of the event. +-- @param DCS#Object Initiator The initiating object of the event. +function BASE:CreateEventUnitLost(EventTime, Initiator) + self:F( { EventTime, Initiator } ) + + local Event = { + id = world.event.S_EVENT_UNIT_LOST, + time = EventTime, + initiator = Initiator, + } + + world.onEvent( Event ) +end + --- Creation of a Dead Event. -- @param #BASE self -- @param DCS#Time EventTime The time stamp of the event. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 9f1f6a9b3..a2c2e2fd4 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -14,7 +14,6 @@ --- AIRWING class. -- @type AIRWING -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity of output. -- @field #string lid Class id string for output to DCS log file. -- @field #table menu Table of menu items. @@ -109,7 +108,6 @@ -- @field #AIRWING AIRWING = { ClassName = "AIRWING", - Debug = false, verbose = 0, lid = nil, menu = nil, @@ -394,7 +392,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) end -- Debug. - if self.Debug then + if self.verbose>=4 then self:I(self.lid..string.format("Looking for payload for unit type=%s and mission type=%s", UnitType, MissionType)) for i,_payload in pairs(self.payloads) do local payload=_payload --#AIRWING.Payload @@ -456,7 +454,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) end -- Debug. - if self.Debug then + if self.verbose>=4 then self:I(self.lid..string.format("Sorted payloads for mission type X and aircraft type=Y:")) for _,_payload in ipairs(self.payloads) do local payload=_payload --#AIRWING.Payload @@ -576,7 +574,7 @@ end --- Remove asset from squadron. -- @param #AIRWING self --- @param #AIRWING.SquadronAsset Asset +-- @param #AIRWING.SquadronAsset Asset The squad asset. function AIRWING:RemoveAssetFromSquadron(Asset) local squad=self:GetSquadronOfAsset(Asset) if squad then @@ -1541,7 +1539,9 @@ function AIRWING:onafterSquadAssetReturned(From, Event, To, Squadron, Asset) self:T(self.lid..string.format("Asset %s from squadron %s returned! asset.assignment=\"%s\"", Asset.spawngroupname, Squadron.name, tostring(Asset.assignment))) -- Stop flightgroup. - Asset.flightgroup:Stop() + if Asset.flightgroup and not Asset.flightgroup:IsStopped() then + Asset.flightgroup:Stop() + end -- Return payload. self:ReturnPayloadFromAsset(Asset) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 5339dd03f..b3d25e27b 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2552,10 +2552,8 @@ end -- @param #string To To state. -- @param Ops.AirWing#AIRWING.SquadronAsset Asset The asset. function AUFTRAG:onafterAssetDead(From, Event, To, Asset) - - -- Remove opsgroup from mission. - --self:DelOpsGroup(Asset.opsgroup) - + + -- Number of groups alive. local N=self:CountOpsGroups() -- All assets dead? @@ -2568,15 +2566,22 @@ function AUFTRAG:onafterAssetDead(From, Event, To, Asset) else - self:E(self.lid.."ERROR: All assets are dead not but mission was already over... Investigate!") + --self:E(self.lid.."ERROR: All assets are dead not but mission was already over... Investigate!") -- Now this can happen, because when a opsgroup dies (sometimes!), the mission is DONE end end - -- Remove asset from airwing. + -- Asset belonged to an airwing. if self.airwing then - self.airwing:RemoveAssetFromSquadron(Asset) + + if self.Ncasualties==self.Nelements then + -- All elements were destroyed ==> Asset is gone. + self.airwing:RemoveAssetFromSquadron(Asset) + else + -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. + self.airwing:AddAsset(Asset.flightgroup.group, 1) + end end -- Delete asset from mission. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 38de11853..886eee119 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -46,6 +46,7 @@ -- @field #boolean ishelo If true, the is a helicopter group. -- @field #number callsignName Callsign name. -- @field #number callsignNumber Callsign number. +-- @field #number Ndestroyed Number of destroyed units. -- -- @extends Ops.OpsGroup#OPSGROUP @@ -132,6 +133,7 @@ FLIGHTGROUP = { Tparking = nil, menu = nil, ishelo = nil, + Ndestroyed = 0, } @@ -1346,15 +1348,9 @@ end -- @param #string To To state. -- @param #FLIGHTGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementDestroyed(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s.", Element.name)) - - -- Cancel all missions. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - mission:ElementDestroyed(self, Element) - - end + -- Call OPSGROUP function. + self:GetParent(self).onafterElementDestroyed(self, From, Event, To, Element) end @@ -1365,7 +1361,9 @@ end -- @param #string To To state. -- @param #FLIGHTGROUP.Element Element The flight group element. function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s.", Element.name)) + + -- Call OPSGROUP function. + self:GetParent(self).onafterElementDead(self, From, Event, To, Element) if self.flightcontrol and Element.parking then self.flightcontrol:SetParkingFree(Element.parking) @@ -1373,9 +1371,7 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) -- Not parking any more. Element.parking=nil - - -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) + end @@ -1569,7 +1565,6 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Wrapper.Airbase#AIRBASE airbase The airbase the flight landed. function FLIGHTGROUP:onafterLandedAt(From, Event, To) self:I(self.lid..string.format("Flight landed at")) end @@ -1601,11 +1596,6 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterDead(From, Event, To) - self:T(self.lid..string.format("Flight dead!")) - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false -- Remove flight from all FC queues. if self.flightcontrol then @@ -1613,17 +1603,9 @@ function FLIGHTGROUP:onafterDead(From, Event, To) self.flightcontrol=nil end - -- Cancel all missions. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - self:MissionCancel(mission) - mission:GroupDead(self) - - end - - -- Stop - self:Stop() + -- Call OPSGROUP function. + self:GetParent(self).onafterDead(self, From, Event, To) + end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 91d0168ba..7fe4b81d5 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -491,12 +491,20 @@ function OPSGROUP:GetDCSGroup() return DCSGroup end ---- Get DCS GROUP object. +--- Get MOOSE UNIT object. -- @param #OPSGROUP self --- @return DCS#Group DCS group object. +-- @param #number UnitNumber Number of the unit in the group. Default first unit. +-- @return Wrapper.Unit#UNIT The MOOSE UNIT object. function OPSGROUP:GetUnit(UnitNumber) - local DCSGroup=Group.getByName(self.groupname) - return DCSGroup + + local DCSUnit=self:GetDCSUnit(UnitNumber) + + if DCSUnit then + local unit=UNIT:Find(DCSUnit) + return unit + end + + return nil end --- Get DCS GROUP object. @@ -515,15 +523,68 @@ function OPSGROUP:GetDCSUnit(UnitNumber) return nil end ---- Despawn group. +--- Get DCS units. -- @param #OPSGROUP self --- @return #OPSGROUP self -function OPSGROUP:DespawnGroup() +-- @return #list DCS units. +function OPSGROUP:GetDCSUnits() local DCSGroup=self:GetDCSGroup() if DCSGroup then + local units=DCSGroup:getUnits() + return units + end + + return nil +end + +--- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:Despawn() + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + + -- Destroy DCS group. DCSGroup:destroy() + + -- Get all units. + local units=self:GetDCSUnits() + + -- Create a "Remove Unit" event. + local EventTime=timer.getTime() + for i=1,#units do + self:CreateEventRemoveUnit(EventTime, units[i]) + end + end + + return self +end + +--- Destroy group. The whole group is despawned and a "Unit Lost" event is generated for all current units. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:Destroy() + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + + self:I(self.lid.."Destroying group ") + + -- Destroy DCS group. + DCSGroup:destroy() + + -- Get all units. + local units=self:GetDCSUnits() + + -- Create a "Unit Lost" event. + local EventTime=timer.getTime() + for i=1,#units do + self:CreateEventUnitLost(EventTime, units[i]) + end end return self @@ -538,6 +599,7 @@ function OPSGROUP:DespawnUnit(UnitName) if DCSGroup then DCSGroup:destroy() + self:CreateEventRemoveUnit(timer.getTime(), DCSObject) end return self @@ -2347,7 +2409,7 @@ function OPSGROUP:_QueueUpdate() --- -- First check if group is alive? Late activated groups are activated and uncontrolled units are started automatically. - if self:IsAlive()~=nil then + if self:IsExist() then local mission=self:_GetNextMission() @@ -2564,6 +2626,67 @@ function OPSGROUP:onafterLeaveZone(From, Event, To, Zone) self.inzones:Remove(zonename, true) end + +--- On after "ElementDestroyed" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Element The flight group element. +function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) + self:I(self.lid..string.format("Element destroyed %s", Element.name)) + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + mission:ElementDestroyed(self, Element) + + end + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) + +end + +--- On after "ElementDead" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #OPSGROUP.Element Element The flight group element. +function OPSGROUP:onafterElementDead(From, Event, To, Element) + self:I(self.lid..string.format("Element dead %s", Element.name)) + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) +end + +--- On after "Dead" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterDead(From, Event, To) + self:T(self.lid..string.format("Flight dead!")) + + -- Delete waypoints so they are re-initialized at the next spawn. + self.waypoints=nil + self.groupinitialized=false + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + self:MissionCancel(mission) + mission:GroupDead(self) + + end + + -- Stop + self:Stop() +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3427,6 +3550,7 @@ function OPSGROUP:GetTACAN() return self.tacan.Channel, self.tacan.Morse, self.tacan.Band, self.tacan.On, self.tacan.BeaconName end + --- Activate/switch ICLS beacon settings. -- @param #OPSGROUP self -- @param #OPSGROUP.Beacon Icls ICLS data table. @@ -3628,6 +3752,8 @@ function OPSGROUP:TurnOffRadio() return self end + + --- Set default formation. -- @param #OPSGROUP self -- @param #number Formation The formation the groups flies in. @@ -3673,6 +3799,8 @@ function OPSGROUP:SwitchFormation(Formation) return self end + + --- Set default formation. -- @param #OPSGROUP self -- @param #number CallsignName Callsign name. diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index a711f754c..bb620d089 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -17,7 +17,6 @@ --- SQUADRON class. -- @type SQUADRON -- @field #string ClassName Name of the class. --- @field #boolean Debug Debug mode. Messages to all about status. -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field #string name Name of the squadron. @@ -63,8 +62,7 @@ -- @field #SQUADRON SQUADRON = { ClassName = "SQUADRON", - Debug = nil, - verbose = 0, + verbose = 3, lid = nil, name = nil, templatename = nil, @@ -199,7 +197,6 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) -- Debug trace. if false then - self.Debug=true BASE:TraceOnOff(true) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) @@ -658,7 +655,7 @@ function SQUADRON:_CheckAssetStatus() local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset -- Text. - text=text..string.format("\n-[%d] %s*%d: ", j, asset.unittype, asset.nunits) + text=text..string.format("\n-[%d] %s (%s*%d): ", j, asset.spawngroupname, asset.unittype, asset.nunits) if asset.spawned then @@ -670,7 +667,7 @@ function SQUADRON:_CheckAssetStatus() local mission=self.airwing and self.airwing:GetAssetCurrentMission(asset) or false if mission then local distance=asset.flightgroup and UTILS.MetersToNM(mission:GetTargetDistance(asset.flightgroup.group:GetCoordinate())) or 0 - text=text..string.format(" Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) + text=text..string.format("Mission %s - %s: Status=%s, Dist=%.1f NM", mission.name, mission.type, mission.status, distance) else text=text.."Mission None" end @@ -708,15 +705,24 @@ function SQUADRON:_CheckAssetStatus() --- -- In Stock --- + + text=text..string.format("In Stock") + + if self:IsRepaired(asset) then + text=text.." and Combat Ready" + else + + text=text..string.format(", Repaired in %d sec", self:GetRepairTime(asset)) + + if asset.damage then + text=text..string.format(" (Damage=%.1f)", asset.damage) + end + end if asset.Treturned then local T=timer.getAbsTime()-asset.Treturned - text=text..string.format(" Treturn=%d sec", T) + text=text..string.format(", Returned for %d sec", T) end - if asset.damage then - text=text..string.format(" Damage=%.1f", asset.damage) - end - text=text..string.format(" Repaired=%s T=%d sec", tostring(self:IsRepaired(asset)), self:GetRepairTime(asset)) end end From 18e192b235d97d10ff811903bdd5530c8490b0fd Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 6 Sep 2020 00:18:20 +0200 Subject: [PATCH 66/79] Ops --- Moose Development/Moose/Ops/Auftrag.lua | 12 -- Moose Development/Moose/Ops/FlightGroup.lua | 54 +++++---- Moose Development/Moose/Ops/OpsGroup.lua | 128 ++++++++++++++------ Moose Development/Moose/Ops/Squadron.lua | 22 +++- 4 files changed, 144 insertions(+), 72 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index b3d25e27b..82786e869 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2571,18 +2571,6 @@ function AUFTRAG:onafterAssetDead(From, Event, To, Asset) end end - - -- Asset belonged to an airwing. - if self.airwing then - - if self.Ncasualties==self.Nelements then - -- All elements were destroyed ==> Asset is gone. - self.airwing:RemoveAssetFromSquadron(Asset) - else - -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. - self.airwing:AddAsset(Asset.flightgroup.group, 1) - end - end -- Delete asset from mission. self:DelAsset(Asset) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 886eee119..55d66f504 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -46,7 +46,7 @@ -- @field #boolean ishelo If true, the is a helicopter group. -- @field #number callsignName Callsign name. -- @field #number callsignNumber Callsign number. --- @field #number Ndestroyed Number of destroyed units. +-- @field #boolean despawnAfterLanding If true, group is despawned after landed at an airbase. -- -- @extends Ops.OpsGroup#OPSGROUP @@ -133,7 +133,6 @@ FLIGHTGROUP = { Tparking = nil, menu = nil, ishelo = nil, - Ndestroyed = 0, } @@ -497,6 +496,14 @@ function FLIGHTGROUP:SetFuelCriticalRTB(switch) return self end +--- Enable that the group is despawned after landing. This can be useful to avoid DCS taxi issues with other AI or players or jamming taxiways. +-- @param #FLIGHTGROUP self +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetDespawnAfterLanding() + self.despawnAfterLanding=true + return self +end + --- Check if flight is parking. -- @param #FLIGHTGROUP self @@ -1557,6 +1564,10 @@ function FLIGHTGROUP:onafterLanded(From, Event, To, airbase) -- Add flight to taxiinb queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB) end + + if self.despawnAfterLanding then + self:Despawn() + end end @@ -1602,6 +1613,18 @@ function FLIGHTGROUP:onafterDead(From, Event, To) self.flightcontrol:_RemoveFlight(self) self.flightcontrol=nil end + + if self.Ndestroyed==#self.elements then + if self.squadron then + -- All elements were destroyed ==> Asset group is gone. + self.squadron:DelGroup(self.groupname) + end + else + if self.airwing then + -- Not all assets were destroyed (despawn) ==> Add asset back to airwing. + self.airwing:AddAsset(self.group, 1) + end + end -- Call OPSGROUP function. self:GetParent(self).onafterDead(self, From, Event, To) @@ -2407,31 +2430,16 @@ function FLIGHTGROUP:onafterStop(From, Event, To) end -- Destroy group. No event is generated. - self.group:Destroy(false) + -- DISABLED for now. Should use :Despawn() or :Destroy() which then calls stop. + --self.group:Destroy(false) end - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.EngineStartup) - self:UnHandleEvent(EVENTS.Takeoff) - self:UnHandleEvent(EVENTS.Land) - self:UnHandleEvent(EVENTS.EngineShutdown) - self:UnHandleEvent(EVENTS.PilotDead) - self:UnHandleEvent(EVENTS.Ejection) - self:UnHandleEvent(EVENTS.Crash) - self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Stop check timers. - self.timerCheckZone:Stop() - self.timerQueueUpdate:Stop() - - -- Stop FSM scheduler. - self.CallScheduler:Clear() - -- Remove flight from data base. _DATABASE.FLIGHTGROUPS[self.groupname]=nil - - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + + + -- Call OPSGROUP function. + self:GetParent(self).onafterStop(self, From, Event, To) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7fe4b81d5..1dbfccef9 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -40,7 +40,7 @@ -- @field #string attribute Generalized attribute. -- @field #number speedMax Max speed in km/h. -- @field #number speedCruise Cruising speed in km/h. --- @field #number speedWp Speed to the next waypoint in km/h. +-- @field #number speedWp Speed to the next waypoint in m/s. -- @field #boolean passedfinalwp Group has passed the final waypoint. -- @field #number wpcounter Running number counting waypoints. -- @field #boolean respawning Group is being respawned. @@ -51,6 +51,7 @@ -- @field #boolean groupinitialized If true, group parameters were initialized. -- @field #boolean detectionOn If true, detected units of the group are analyzed. -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. +-- @field #number Ndestroyed Number of destroyed units. -- -- @field Core.Point#COORDINATE coordinate Current coordinate. -- @field Core.Point#COORDINATE position Position of the group at last status check. @@ -126,6 +127,7 @@ OPSGROUP = { tacan = {}, icls = {}, callsign = {}, + Ndestroyed = 0, } --- Status of group element. @@ -540,51 +542,73 @@ end --- Despawn the group. The whole group is despawned and (optionally) a "Remove Unit" event is generated for all current units of the group. -- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before the group will be despawned. Default immediately. +-- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. -- @return #OPSGROUP self -function OPSGROUP:Despawn() +function OPSGROUP:Despawn(Delay, NoEventRemoveUnit) - local DCSGroup=self:GetDCSGroup() - - if DCSGroup then - - -- Destroy DCS group. - DCSGroup:destroy() - - -- Get all units. - local units=self:GetDCSUnits() + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.Despawn, self, 0, NoEventRemoveUnit) + else - -- Create a "Remove Unit" event. - local EventTime=timer.getTime() - for i=1,#units do - self:CreateEventRemoveUnit(EventTime, units[i]) + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + + -- Destroy DCS group. + DCSGroup:destroy() + + if not NoEventRemoveUnit then + + -- Get all units. + local units=self:GetDCSUnits() + + -- Create a "Remove Unit" event. + local EventTime=timer.getTime() + for i=1,#units do + self:CreateEventRemoveUnit(EventTime, units[i]) + end + + end end end return self end ---- Destroy group. The whole group is despawned and a "Unit Lost" event is generated for all current units. +--- Destroy group. The whole group is despawned and a *Unit Lost* for aircraft or *Dead* event for ground/naval units is generated for all current units. -- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before the group will be destroyed. Default immediately. -- @return #OPSGROUP self -function OPSGROUP:Destroy() +function OPSGROUP:Destroy(Delay) - local DCSGroup=self:GetDCSGroup() - - if DCSGroup then - - self:I(self.lid.."Destroying group ") - - -- Destroy DCS group. - DCSGroup:destroy() - - -- Get all units. - local units=self:GetDCSUnits() - - -- Create a "Unit Lost" event. - local EventTime=timer.getTime() - for i=1,#units do - self:CreateEventUnitLost(EventTime, units[i]) + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.Destroy, self) + else + + local DCSGroup=self:GetDCSGroup() + + if DCSGroup then + + self:I(self.lid.."Destroying group") + + -- Destroy DCS group. + DCSGroup:destroy() + + -- Get all units. + local units=self:GetDCSUnits() + + -- Create a "Unit Lost" event. + local EventTime=timer.getTime() + for i=1,#units do + if self.isAircraft then + self:CreateEventUnitLost(EventTime, units[i]) + else + self:CreateEventDead(EventTime, units[i]) + end + end end + end return self @@ -2642,7 +2666,10 @@ function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) mission:ElementDestroyed(self, Element) - end + end + + -- Increase counter. + self.Ndestroyed=self.Ndestroyed+1 -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -2687,6 +2714,39 @@ function OPSGROUP:onafterDead(From, Event, To) self:Stop() end +--- On after "Stop" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onafterStop(From, Event, To) + + -- Handle events: + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.EngineStartup) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.EngineShutdown) + self:UnHandleEvent(EVENTS.PilotDead) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.Crash) + self:UnHandleEvent(EVENTS.RemoveUnit) + + -- Stop check timers. + self.timerCheckZone:Stop() + self.timerQueueUpdate:Stop() + + -- Stop FSM scheduler. + self.CallScheduler:Clear() + + if self:IsAlive() then + self:E(self.lid.."WARNING: Group is still alive! Use OPSGROUP:Destroy() or OPSGROUP:Despawn() for a clean stop") + end + + -- Debug output. + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Internal Check Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index bb620d089..a9a7ce6ce 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -448,6 +448,22 @@ function SQUADRON:DelAsset(Asset) return self end +--- Remove airwing asset group from squadron. +-- @param #SQUADRON self +-- @param #string GroupName Name of the asset group. +-- @return #SQUADRON self +function SQUADRON:DelGroup(GroupName) + for i,_asset in pairs(self.assets) do + local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset + if GroupName==asset.spawngroupname then + self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname)) + table.remove(self.assets, i) + break + end + end + return self +end + --- Get name of the squadron -- @param #SQUADRON self -- @return #string Name of the squadron. @@ -648,14 +664,14 @@ end -- @param #SQUADRON self function SQUADRON:_CheckAssetStatus() - if self.verbose>=2 then + if self.verbose>=2 and #self.assets>0 then local text="" for j,_asset in pairs(self.assets) do local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset -- Text. - text=text..string.format("\n-[%d] %s (%s*%d): ", j, asset.spawngroupname, asset.unittype, asset.nunits) + text=text..string.format("\n[%d] %s (%s*%d): ", j, asset.spawngroupname, asset.unittype, asset.nunits) if asset.spawned then @@ -709,7 +725,7 @@ function SQUADRON:_CheckAssetStatus() text=text..string.format("In Stock") if self:IsRepaired(asset) then - text=text.." and Combat Ready" + text=text..", Combat Ready" else text=text..string.format(", Repaired in %d sec", self:GetRepairTime(asset)) From 7b8db597ef70f18f27351d3f246cceb8f750c6d8 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 7 Sep 2020 00:42:29 +0200 Subject: [PATCH 67/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 200 +++++++------------ Moose Development/Moose/Ops/FlightGroup.lua | 75 ++++--- Moose Development/Moose/Ops/NavyGroup.lua | 208 +++----------------- Moose Development/Moose/Ops/OpsGroup.lua | 160 ++++++++++++--- Moose Development/Moose/Ops/Squadron.lua | 2 +- 5 files changed, 283 insertions(+), 362 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 8bfe636fa..5af542aaf 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -141,18 +141,6 @@ function ARMYGROUP:SetPatrolAdInfinitum(switch) return self end ---- Set default cruise speed. This is the speed a group will take by default if no speed is specified explicitly. --- @param #ARMYGROUP self --- @param #number Speed Speed in knots. Default 70% of max speed. --- @return #ARMYGROUP self -function ARMYGROUP:SetSpeedCruise(Speed) - - self.speedCruise=Speed and UTILS.KnotsToKmph(Speed) or self.speedMax*0.7 - - return self -end - - --- Get coordinate of the closest road. -- @param #ARMYGROUP self -- @return Core.Point#COORDINATE Coordinate of a road closest to the group. @@ -212,7 +200,26 @@ function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo end +--- Check if the group is currently holding its positon. +-- @param #ARMYGROUP self +-- @return #boolean If true, group was ordered to hold. +function ARMYGROUP:IsHolding() + return self:Is("Holding") +end +--- Check if the group is currently cruising. +-- @param #ARMYGROUP self +-- @return #boolean If true, group cruising. +function ARMYGROUP:IsCruising() + return self:Is("Cruising") +end + +--- Check if the group is currently on a detour. +-- @param #ARMYGROUP self +-- @return #boolean If true, group is on a detour +function ARMYGROUP:IsOnDetour() + return self:Is("OnDetour") +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status @@ -223,10 +230,10 @@ end function ARMYGROUP:onbeforeStatus(From, Event, To) if self:IsDead() then - self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) + self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) return false elseif self:IsStopped() then - self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) + self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) return false end @@ -251,27 +258,36 @@ function ARMYGROUP:onafterStatus(From, Event, To) self:_CheckDetectedUnits() end - - -- Current heading and position of the carrier. - local hdg=self:GetHeading() - local pos=self:GetCoordinate() - local speed=self.group:GetVelocityKNOTS() + -- Update position etc. + self:_UpdatePosition() + -- Check if group got stuck. + self:_CheckStuck() + + if self.verbose>=1 then - -- Get number of tasks and missions. - local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() - local nMissions=self:CountRemainingMissison() - - -- Info text. - local text=string.format("%s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", - fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, UTILS.MpsToKnots(self.speedWp or 0), hdg, self.option.ROE, self.option.Alarm, self.option.Formation, nTaskTot, nMissions) - self:I(self.lid..text) + -- Get number of tasks and missions. + local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() + local nMissions=self:CountRemainingMissison() + + local roe=self:GetROE() + local alarm=self:GetAlarmstate() + local speed=UTILS.MpsToKnots(self.velocity) + local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) + local formation=self.option.Formation + + -- Info text. + local text=string.format("%s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", + fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, speedEx, self.heading, roe, alarm, formation, nTaskTot, nMissions) + self:I(self.lid..text) + + end else -- Info text. local text=string.format("State %s: Alive=%s", fsmstate, tostring(self:IsAlive())) - self:I(self.lid..text) + self:T2(self.lid..text) end @@ -305,19 +321,6 @@ function ARMYGROUP:onafterElementSpawned(From, Event, To, Element) end ---- On after "ElementDead" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #ARMYGROUP.Element Element The group element. -function ARMYGROUP:onafterElementDead(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s.", Element.name)) - - -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) -end - --- On after "Spawned" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -326,10 +329,8 @@ end function ARMYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) - -- TODO - self.traveldist=0 - self.traveltime=timer.getAbsTime() - self.position=self:GetCoordinate() + -- Update position. + self:_UpdatePosition() if self.ai then @@ -369,9 +370,6 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) -- Update route from this waypoint number onwards. n=n or self:GetWaypointIndexNext(self.adinfinitum) - -- Debug info. - --self:I(self.lid..string.format("FF Update route n=%d", n)) - -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. self:_UpdateWaypointTasks(n) @@ -428,8 +426,6 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) end if wp.roaddist>100 and wp.action==ENUMS.Formation.Vehicle.OnRoad then - env.info("FF Adding ON road waypoint") - --wp.roadcoord:MarkToAll("Added Road waypoint") -- Waypoint is actually off road! wp.action=ENUMS.Formation.Vehicle.OffRoad @@ -439,17 +435,8 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) table.insert(waypoints, wproad) end - --if wp.formation==ENUMS.Formation.Vehicle.OnRoad and wp.action~=ENUMS.Formation.Vehicle.OnRoad then --and not self.formationPerma~=ENUMS.Formation.Vehicle.OnRoad then - --[[ - if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>100 then - env.info("FF Adding ON road waypoint") - local wproad=wp.roadcoord:WaypointGround(wp.speed, ENUMS.Formation.Vehicle.OnRoad) - table.insert(waypoints, wproad) - end - ]] - -- Debug info. - self:I(string.format("WP %d %s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.type, wp.speed, wp.alt, wp.action)) + self:T(string.format("WP %d %s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.type, wp.speed, wp.alt, wp.action)) -- Add waypoint. table.insert(waypoints, wp) @@ -463,7 +450,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) if #waypoints>2 then - self:I(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Formation=%s", + self:T(self.lid..string.format("Updateing route: WP %d-->%d-->%d (#%d), Speed=%.1f knots, Formation=%s", self.currentwp, n, #self.waypoints, #waypoints-2, UTILS.MpsToKnots(self.speedWp), tostring(self.option.Formation))) -- Route group to all defined waypoints remaining. @@ -552,31 +539,6 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) end ---- On after "Dead" event. --- @param #ARMYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function ARMYGROUP:onafterDead(From, Event, To) - self:I(self.lid..string.format("Group dead!")) - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false - - -- Cancel all mission. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - self:MissionCancel(mission) - mission:GroupDead(self) - - end - - -- Stop - self:Stop() -end - --- On after Start event. Starts the ARMYGROUP FSM and event handlers. -- @param #ARMYGROUP self -- @param #string From From state. @@ -584,25 +546,14 @@ end -- @param #string To To state. function ARMYGROUP:onafterStop(From, Event, To) - -- Check if group is still alive. - if self:IsAlive() then - -- Destroy group. No event is generated. - self.group:Destroy(false) - end - -- Handle events: self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Stop check timers. - self.timerCheckZone:Stop() - self.timerQueueUpdate:Stop() - - -- Stop FSM scheduler. - self.CallScheduler:Clear() - - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + + -- Call OPSGROUP function. + self:GetParent(self).onafterStop(self, From, Event, To) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -662,8 +613,8 @@ function ARMYGROUP:OnEventDead(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s dead ==> dead", element.name)) - self:ElementDead(element) + self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) + self:ElementDestroyed(element) end end @@ -685,7 +636,7 @@ function ARMYGROUP:OnEventRemoveUnit(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) + self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) self:ElementDead(element) end @@ -734,19 +685,9 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation else waypoint.roaddist=1000*1000 --1000 km. end - - --[[ - if waypoint.roaddist>100 and waypoint.action==ENUMS.Formation.Vehicle.OnRoad then - waypoint.formation=ENUMS.Formation.Vehicle.OnRoad - waypoint.action=ENUMS.Formation.Vehicle.OffRoad - else - waypoint.formation=waypoint.action - end - ]] - -- Debug info. - self:I(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action)) + self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action)) -- Update route. if Updateroute==nil or Updateroute==true then @@ -836,18 +777,20 @@ function ARMYGROUP:_InitGroup() self.actype=unit:GetTypeName() -- Debug info. - local text=string.format("Initialized Army Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - self:I(self.lid..text) + if self.verbose>=1 then + local text=string.format("Initialized Army Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + end -- Init done. self.groupinitialized=true @@ -885,7 +828,7 @@ function ARMYGROUP:SwitchFormation(Formation, Permanently) self:__UpdateRoute(-1, nil, nil, Formation) -- Debug info. - self:I(self.lid..string.format("Switching formation to %s (permanently=%s)", self.option.Formation, tostring(Permanently))) + self:T(self.lid..string.format("Switching formation to %s (permanently=%s)", self.option.Formation, tostring(Permanently))) end @@ -897,6 +840,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 55d66f504..b8286043f 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -704,6 +704,21 @@ end -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +---- Update status. +-- @param #FLIHGTGROUP self +function FLIGHTGROUP:onbeforeStatus(From, Event, To) + + if self:IsDead() then + self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) + return false + elseif self:IsStopped() then + self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) + return false + end + + return true +end + --- On after "Status" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -713,6 +728,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- FSM state. local fsmstate=self:GetState() + + -- Update position. + self:_UpdatePosition() --- -- Detection @@ -752,21 +770,27 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end --- - -- Elements + -- Group --- - local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() - local nMissions=self:CountRemainingMissison() - -- Short info. if self.verbose>=1 then + + local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() + local nMissions=self:CountRemainingMissison() + + local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Curr=%d, Missions=%s, Waypoint=%d/%d, Detected=%d, Home=%s, Destination=%s", fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, self.detectedunits:Count(), self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "unknown") self:I(self.lid..text) + end - -- Element status. + --- + -- Elements + --- + if self.verbose>=2 then local text="Elements:" for i,_element in pairs(self.elements) do @@ -801,25 +825,17 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Distance travelled --- - if self.verbose>=3 and self:IsAlive() and self.position then - - local time=timer.getAbsTime() - - -- Current position. - local position=self:GetCoordinate() + if self.verbose>=3 and self:IsAlive() then -- Travelled distance since last check. - local ds=self.position:Get3DDistance(position) + local ds=self.travelds -- Time interval. - local dt=time-self.traveltime + local dt=self.dTpositionUpdate -- Speed. local v=ds/dt - -- Add up travelled distance. - self.traveldist=self.traveldist+ds - -- Max fuel time remaining. local TmaxFuel=math.huge @@ -852,11 +868,7 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) -- Log outut. self:I(self.lid..string.format("Travelled ds=%.1f km dt=%.1f s ==> v=%.1f knots. Fuel left for %.1f min", self.traveldist/1000, dt, UTILS.MpsToKnots(v), TmaxFuel/60)) - - - -- Update parameters. - self.traveltime=time - self.position=position + end --- @@ -911,8 +923,6 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1390,10 +1400,8 @@ end function FLIGHTGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Flight spawned")) - -- TODO: general routine in opsgroup - self.traveldist=0 - self.traveltime=timer.getAbsTime() - self.position=self:GetCoordinate() + -- Update position. + self:_UpdatePosition() if self.ai then @@ -1577,7 +1585,7 @@ end -- @param #string Event Event. -- @param #string To To state. function FLIGHTGROUP:onafterLandedAt(From, Event, To) - self:I(self.lid..string.format("Flight landed at")) + self:T(self.lid..string.format("Flight landed at")) end @@ -2434,6 +2442,17 @@ function FLIGHTGROUP:onafterStop(From, Event, To) --self.group:Destroy(false) end + -- Handle events: + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.EngineStartup) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.EngineShutdown) + self:UnHandleEvent(EVENTS.PilotDead) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.Crash) + self:UnHandleEvent(EVENTS.RemoveUnit) + -- Remove flight from data base. _DATABASE.FLIGHTGROUPS[self.groupname]=nil diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 711b5e223..7ae587da8 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -290,7 +290,7 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) return self end if Tstop<=Tnow then - self:I(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.", UTILS.SecondsToClock(Tstop), UTILS.SecondsToClock(Tnow))) + self:E(string.format("WARNING: Into wind stop time %s already over. Tnow=%s! Input rejected.", UTILS.SecondsToClock(Tstop), UTILS.SecondsToClock(Tnow))) return self end @@ -389,10 +389,10 @@ end function NAVYGROUP:onbeforeStatus(From, Event, To) if self:IsDead() then - self:I(self.lid..string.format("Onbefore Status DEAD ==> false")) + self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) return false elseif self:IsStopped() then - self:I(self.lid..string.format("Onbefore Status STOPPED ==> false")) + self:T(self.lid..string.format("Onbefore Status STOPPED ==> false")) return false end @@ -450,7 +450,7 @@ function NAVYGROUP:onafterStatus(From, Event, To) self:_CheckTurnsIntoWind() -- Check if group got stuck. - self:_CheckStuck() + self:_CheckStuck() if self.verbose>=1 then @@ -518,19 +518,6 @@ function NAVYGROUP:onafterElementSpawned(From, Event, To, Element) end ---- On after "ElementDead" event. --- @param #NAVYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #NAVYGROUP.Element Element The group element. -function NAVYGROUP:onafterElementDead(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s.", Element.name)) - - -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) -end - --- On after "Spawned" event. -- @param #NAVYGROUP self -- @param #string From From state. @@ -539,10 +526,8 @@ end function NAVYGROUP:onafterSpawned(From, Event, To) self:T(self.lid..string.format("Group spawned!")) - -- TODO - self.traveldist=0 - self.traveltime=timer.getAbsTime() - self.position=self:GetCoordinate() + -- Update position. + self:_UpdatePosition() if self.ai then @@ -571,9 +556,6 @@ function NAVYGROUP:onafterSpawned(From, Event, To) end - -- Get orientation. - self.Corientlast=self.group:GetUnit(1):GetOrientationX() - -- Update route. self:Cruise() @@ -742,7 +724,7 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind) local speed=math.max(IntoWind.Speed-vwind, 2) -- Debug info. - self:I(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f Vwind=%.1f Vtot=%.1f knots, Tstart=%d Tstop=%d", IntoWind.Heading, speed, vwind, speed+vwind, IntoWind.Tstart, IntoWind.Tstop)) + self:T(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f Vwind=%.1f Vtot=%.1f knots, Tstart=%d Tstop=%d", IntoWind.Heading, speed, vwind, speed+vwind, IntoWind.Tstart, IntoWind.Tstop)) local distance=UTILS.NMToMeters(1000) @@ -840,7 +822,7 @@ function NAVYGROUP:onafterDive(From, Event, To, Depth, Speed) Depth=Depth or 50 - self:I(self.lid..string.format("Diving to %d meters", Depth)) + self:T(self.lid..string.format("Diving to %d meters", Depth)) self.depth=Depth @@ -888,35 +870,10 @@ end -- @param #string To To state. -- @param #number Distance Distance in meters where obstacle was detected. function NAVYGROUP:onafterCollisionWarning(From, Event, To, Distance) - self:I(self.lid..string.format("Iceberg ahead in %d meters!", Distance or -1)) + self:T(self.lid..string.format("Iceberg ahead in %d meters!", Distance or -1)) self.collisionwarning=true end ---- On after "Dead" event. --- @param #NAVYGROUP self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function NAVYGROUP:onafterDead(From, Event, To) - self:I(self.lid..string.format("Group dead!")) - - -- Delete waypoints so they are re-initialized at the next spawn. - self.waypoints=nil - self.groupinitialized=false - - -- Cancel all mission. - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - self:MissionCancel(mission) - mission:GroupDead(self) - - end - - -- Stop - self:Stop() -end - --- On after Start event. Starts the NAVYGROUP FSM and event handlers. -- @param #NAVYGROUP self -- @param #string From From state. @@ -924,25 +881,14 @@ end -- @param #string To To state. function NAVYGROUP:onafterStop(From, Event, To) - -- Check if group is still alive. - if self:IsAlive() then - -- Destroy group. No event is generated. - self.group:Destroy(false) - end - -- Handle events: self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Stop check timers. - self.timerCheckZone:Stop() - self.timerQueueUpdate:Stop() - - -- Stop FSM scheduler. - self.CallScheduler:Clear() - - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + + -- Call OPSGROUP function. + self:GetParent(self).onafterStop(self, From, Event, To) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1002,8 +948,8 @@ function NAVYGROUP:OnEventDead(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s dead ==> dead", element.name)) - self:ElementDead(element) + self:T(self.lid..string.format("EVENT: Element %s dead ==> destroyed", element.name)) + self:ElementDestroyed(element) end end @@ -1025,7 +971,7 @@ function NAVYGROUP:OnEventRemoveUnit(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) + self:T(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) self:ElementDead(element) end @@ -1173,18 +1119,20 @@ function NAVYGROUP:_InitGroup() self.actype=unit:GetTypeName() -- Debug info. - local text=string.format("Initialized Navy Group %s:\n", self.groupname) - text=text..string.format("Unit type = %s\n", self.actype) - text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) - text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) - text=text..string.format("Elements = %d\n", #self.elements) - text=text..string.format("Waypoints = %d\n", #self.waypoints) - text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) - text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) - text=text..string.format("FSM state = %s\n", self:GetState()) - text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) - text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) - self:I(self.lid..text) + if self.verbose>=1 then + local text=string.format("Initialized Navy Group %s:\n", self.groupname) + text=text..string.format("Unit type = %s\n", self.actype) + text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) + text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise)) + text=text..string.format("Elements = %d\n", #self.elements) + text=text..string.format("Waypoints = %d\n", #self.waypoints) + text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On)) + text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos) + text=text..string.format("FSM state = %s\n", self:GetState()) + text=text..string.format("Is alive = %s\n", tostring(self:IsAlive())) + text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated())) + self:I(self.lid..text) + end -- Init done. self.groupinitialized=true @@ -1269,7 +1217,7 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) local los=LoS(x) -- Debug message. - self:I(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) + self:T2(self.lid..string.format("N=%d: xmin=%.1f xmax=%.1f x=%.1f d=%.3f los=%s", N, xmin, xmax, x, d, tostring(los))) if los and d<=eps then return x @@ -1291,68 +1239,6 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) return check() end ---- Check for possible collisions between two coordinates. --- @param #NAVYGROUP self --- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check. --- @param Core.Point#COORDINATE coordfrom Coordinate from which the collision is check. --- @return #boolean If true, surface type ahead is not deep water. --- @return #number Max free distance in meters. -function NAVYGROUP:_CheckCollisionCoord(coordto, coordfrom) - - -- Increment in meters. - local dx=100 - - -- From coordinate. Default 500 in front of the carrier. - local d=0 - if coordfrom then - d=0 - else - d=250 - coordfrom=self:GetCoordinate():Translate(d, self:GetHeading()) - end - - -- Distance between the two coordinates. - local dmax=coordfrom:Get2DDistance(coordto) - - -- Direction. - local direction=coordfrom:HeadingTo(coordto) - - -- Scan path between the two coordinates. - local clear=true - while d<=dmax do - - -- Check point. - local cp=coordfrom:Translate(d, direction) - - -- Check if surface type is water. - if not cp:IsSurfaceTypeWater() then - - -- Debug mark points. - if self.Debug or true then - local st=cp:GetSurfaceType() - cp:MarkToAll(string.format("Collision check surface type %d", st)) - end - - -- Collision WARNING! - clear=false - break - end - - -- Increase distance. - d=d+dx - end - - local text="" - if clear then - text=string.format("Path into direction %03d° is clear for the next %.1f NM.", direction, UTILS.MetersToNM(d)) - else - text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.", UTILS.MetersToNM(d), direction) - end - self:T(self.lid..text) - - return not clear, d -end - --- Check if group is turning. -- @param #NAVYGROUP self function NAVYGROUP:_CheckTurning() @@ -1365,7 +1251,7 @@ function NAVYGROUP:_CheckTurning() local vNew=self.orientX --unit:GetOrientationX() -- Last orientation from 30 seconds ago. - local vLast=self.orientXLast --self.Corientlast or vNew + local vLast=self.orientXLast -- We only need the X-Z plane. vNew.y=0 ; vLast.y=0 @@ -1373,9 +1259,6 @@ function NAVYGROUP:_CheckTurning() -- Angle between current heading and last time we checked ~30 seconds ago. local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast))) - -- Last orientation becomes new orientation - --self.Corientlast=vNew - -- Carrier is turning when its heading changed by at least two degrees since last check. local turning=math.abs(deltaLast)>=2 @@ -1399,33 +1282,6 @@ function NAVYGROUP:_CheckTurning() end ---- Check if group got stuck. --- @param #NAVYGROUP self -function NAVYGROUP:_CheckStuck() - - if self:IsHolding() then - return - end - - local holdtime=0 - if self.holdtimestamp then - holdtime=timer.getTime()-self.holdtimestamp - end - - local ExpectedSpeed=self:GetExpectedSpeed() - - local speed=self:GetVelocity() - - if speed<0.5 and ExpectedSpeed>0 then - if not self.holdtimestamp then - self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected", speed, ExpectedSpeed)) - self.holdtimestamp=timer.getTime() - end - end - -end - - --- Check queued turns into wind. -- @param #NAVYGROUP self diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 1dbfccef9..bdd2c2f17 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -54,8 +54,14 @@ -- @field #number Ndestroyed Number of destroyed units. -- -- @field Core.Point#COORDINATE coordinate Current coordinate. --- @field Core.Point#COORDINATE position Position of the group at last status check. --- @field #number traveldist Distance traveled in meters. This is a lower bound! +-- +-- @field DCS#Vec3 position Position of the group at last status check. +-- @field DCS#Vec3 positionLast Backup of last position vec to monitor changes. +-- @field #number heading Heading of the group at last status check. +-- @field #number headingLast Backup of last heading to monitor changes. +-- @field DCS#Vec3 orientX Orientation at last status check. +-- @field DCS#Vec3 orientXLast Backup of last orientation to monitor changes. +-- @field #number traveldist Distance traveled in meters. This is a lower bound. -- @field #number traveltime Time. -- -- @field Core.Astar#ASTAR Astar path finding. @@ -590,7 +596,7 @@ function OPSGROUP:Destroy(Delay) if DCSGroup then - self:I(self.lid.."Destroying group") + self:T(self.lid.."Destroying group") -- Destroy DCS group. DCSGroup:destroy() @@ -650,8 +656,9 @@ end --- Get current coordinate of the group. -- @param #OPSGROUP self +-- @param #boolean NewObject Create a new coordiante object. -- @return Core.Point#COORDINATE The coordinate (of the first unit) of the group. -function OPSGROUP:GetCoordinate() +function OPSGROUP:GetCoordinate(NewObject) local vec3=self:GetVec3() @@ -663,7 +670,11 @@ function OPSGROUP:GetCoordinate() self.coordinate.y=vec3.y self.coordinate.z=vec3.z - return self.coordinate + if NewObject then + local coord=COORDINATE:NewFromCoordinate(self.coordinate) + else + return self.coordinate + end else self:E(self.lid.."WARNING: Group is not alive. Cannot get coordinate!") end @@ -725,6 +736,41 @@ function OPSGROUP:GetHeading() return nil end +--- Get current orientation of the first unit in the group. +-- @param #OPSGROUP self +-- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. +-- @return DCS#Vec3 Orientation Y pointing "upwards". +-- @return DCS#Vec3 Orientation Z perpendicular to the "nose". +function OPSGROUP:GetOrientation() + + if self:IsExist() then + + local unit=self:GetDCSUnit() + + if unit then + + local pos=unit:getPosition() + + return pos.x, pos.y, pos.z + end + + else + self:E(self.lid.."WARNING: Group does not exist. Cannot get orientation!") + end + + return nil +end + +--- Get current orientation of the first unit in the group. +-- @param #OPSGROUP self +-- @return DCS#Vec3 Orientation X parallel to where the "nose" is pointing. +function OPSGROUP:GetOrientationX() + + local X,Y,Z=self:GetOrientation() + + return X +end + --- Check if task description is unique. @@ -2658,7 +2704,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDestroyed(From, Event, To, Element) - self:I(self.lid..string.format("Element destroyed %s", Element.name)) + self:T(self.lid..string.format("Element destroyed %s", Element.name)) -- Cancel all missions. for _,_mission in pairs(self.missionqueue) do @@ -2683,7 +2729,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDead(From, Event, To, Element) - self:I(self.lid..string.format("Element dead %s", Element.name)) + self:T(self.lid..string.format("Element dead %s", Element.name)) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -2695,7 +2741,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) - self:T(self.lid..string.format("Flight dead!")) + self:T(self.lid..string.format("Group dead!")) -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil @@ -2710,8 +2756,8 @@ function OPSGROUP:onafterDead(From, Event, To) end - -- Stop - self:Stop() + -- Stop in a sec. + self:__Stop(-1) end --- On after "Stop" event. @@ -2720,17 +2766,6 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterStop(From, Event, To) - - -- Handle events: - self:UnHandleEvent(EVENTS.Birth) - self:UnHandleEvent(EVENTS.EngineStartup) - self:UnHandleEvent(EVENTS.Takeoff) - self:UnHandleEvent(EVENTS.Land) - self:UnHandleEvent(EVENTS.EngineShutdown) - self:UnHandleEvent(EVENTS.PilotDead) - self:UnHandleEvent(EVENTS.Ejection) - self:UnHandleEvent(EVENTS.Crash) - self:UnHandleEvent(EVENTS.RemoveUnit) -- Stop check timers. self.timerCheckZone:Stop() @@ -3915,26 +3950,43 @@ end --- Check if all elements of the group have the same status (or are dead). -- @param #OPSGROUP self --- @param #string unitname Name of unit. +-- @return #OPSGROUP self function OPSGROUP:_UpdatePosition() if self:IsAlive() then - self.positionLast=self.position or self:GetCoordinate() + -- Backup last state to monitor differences. + self.positionLast=self.position or self:GetVec3() self.headingLast=self.heading or self:GetHeading() - self.orientXLast=self.orientX or self.group:GetUnit(1):GetOrientationX() + self.orientXLast=self.orientX or self:GetOrientationX() self.velocityLast=self.velocity or self.group:GetVelocityMPS() - self.position=self:GetCoordinate() + -- Current state. + self.position=self:GetVec3() self.heading=self:GetHeading() - self.orientX=self.group:GetUnit(1):GetOrientationX() - self.velocity=self.group:GetVelocityMPS() + self.orientX=self:GetOrientationX() + self.velocity=self:GetVelocity() - self.dTpositionUpdate=self.TpositionUpdate and self.TpositionUpdate-timer.getAbsTime() or 0 - self.TpositionUpdate=timer.getAbsTime() + -- Update time. + local Tnow=timer.getTime() + self.dTpositionUpdate=self.TpositionUpdate and Tnow-self.TpositionUpdate or 0 + self.TpositionUpdate=Tnow + + if not self.traveldist then + self.traveldist=0 + end + + self.travelds=UTILS.VecNorm(UTILS.VecSubstract(self.position, self.positionLast)) + + -- Add up travelled distance. + + self.traveldist=self.traveldist+self.travelds + + env.info(string.format("FF Traveled %.1f m", self.traveldist)) end + return self end --- Check if all elements of the group have the same status (or are dead). @@ -4483,6 +4535,56 @@ function OPSGROUP:_MissileCategoryName(categorynumber) return cat end +--- Check if group got stuck. +-- @param #OPSGROUP self +function OPSGROUP:_CheckStuck() + + -- Holding means we are not stuck. + if self:IsHolding() then + return + end + + -- Current time. + local Tnow=timer.getTime() + + -- Expected speed in m/s. + local ExpectedSpeed=self:GetExpectedSpeed() + + -- Current speed in m/s. + local speed=self:GetVelocity() + + -- Check speed. + if speed<0.5 then + + if ExpectedSpeed>0 and not self.stuckTimestamp then + self:T2(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected", speed, ExpectedSpeed)) + self.stuckTimestamp=Tnow + self.stuckVec3=self:GetVec3() + end + + else + -- Moving (again). + self.stuckTimestamp=nil + end + + -- Somehow we are not moving... + if self.stuckTimestamp then + + -- Time we are holding. + local holdtime=Tnow-self.stuckTimestamp + + if holdtime>=5*60 then + + self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) + + --TODO: Stuck event! + + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index a9a7ce6ce..d7a5ca114 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -62,7 +62,7 @@ -- @field #SQUADRON SQUADRON = { ClassName = "SQUADRON", - verbose = 3, + verbose = 0, lid = nil, name = nil, templatename = nil, From bcaf808b88ef22392d041d189c995c67e86a9d65 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 7 Sep 2020 22:17:33 +0200 Subject: [PATCH 68/79] Ops little fixes --- Moose Development/Moose/Core/Point.lua | 8 ++------ Moose Development/Moose/Ops/Auftrag.lua | 21 ++++++++++++++------- Moose Development/Moose/Ops/FlightGroup.lua | 16 +++++++++++++--- Moose Development/Moose/Ops/OpsGroup.lua | 12 ++++++------ 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ca1529950..8d4bf015c 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -774,12 +774,8 @@ do -- COORDINATE local a={x=TargetCoordinate.x-self.x, y=0, z=TargetCoordinate.z-self.z} - return UTILS.VecNorm(a) - - --local TargetVec3 = TargetCoordinate:GetVec3() - --local SourceVec3 = self:GetVec3() - - --return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + local norm=UTILS.VecNorm(a) + return norm end --- Returns the temperature in Degrees Celsius. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 82786e869..a98f3b52c 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -94,6 +94,7 @@ -- @field #number missionAltitude Mission altitude in meters. -- @field #number missionFraction Mission coordiante fraction. Default is 0.5. -- @field #number missionRange Mission range in meters. Used in AIRWING class. +-- @field Core.Point#COORDINATE missionWaypointCoord Mission waypoint coordinate. -- -- @field #table enrouteTasks Mission enroute tasks. -- @@ -1061,7 +1062,7 @@ end --- Create an ESCORT (or FOLLOW) mission. Flight will escort another group and automatically engage certain target types. -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP EscortGroup The group to escort. --- @param DCS#Vec3 OffsetVector A table with x, y and z components specifying the offset of the flight to the escorted group. Default {x=200, y=0, z=-100} for 200 meters to the right, same alitude, 100 meters behind. +-- @param DCS#Vec3 OffsetVector A table with x, y and z components specifying the offset of the flight to the escorted group. Default {x=-100, y=0, z=200} for z=200 meters to the right, same alitude, x=100 meters behind. -- @param #number EngageMaxDistance Max engage distance of targets in meters. Default auto (*nil*). -- @param #table TargetTypes Types of targets to engage automatically. Default is {"Air"}, i.e. all enemy airborne units. Use an empty set {} for a simple "FOLLOW" mission. -- @return #AUFTRAG self @@ -1072,7 +1073,7 @@ function AUFTRAG:NewESCORT(EscortGroup, OffsetVector, EngageMaxDistance, TargetT mission:_TargetFromObject(EscortGroup) -- DCS task parameters: - mission.escortVec3=OffsetVector or {x=200, y=0, z=-100} + mission.escortVec3=OffsetVector or {x=-100, y=0, z=200} mission.engageMaxDistance=EngageMaxDistance mission.engageTargetTypes=TargetTypes or {"Air"} @@ -2140,9 +2141,9 @@ function AUFTRAG:Evaluate() --- -- Check if failed. - if self.type==AUFTRAG.Type.TROOPTRANSPORT then + if self.type==AUFTRAG.Type.TROOPTRANSPORT or self.type==AUFTRAG.Type.ESCORT then - -- Transported groups have to survive. + -- Transported or escorted groups have to survive. if Ntargets Date: Tue, 8 Sep 2020 00:15:29 +0200 Subject: [PATCH 69/79] Ops temp --- Moose Development/Moose/Core/Timer.lua | 2 +- Moose Development/Moose/Ops/FlightGroup.lua | 11 +++++------ Moose Development/Moose/Ops/Squadron.lua | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 9b8a9200b..8154bcc5a 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -206,7 +206,7 @@ function TIMER:Stop(Delay) if self.tid then -- Remove timer function. - self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) + self:I(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) timer.removeFunction(self.tid) -- Remove DB entry. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 471751a36..8bb75be4f 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2452,13 +2452,12 @@ function FLIGHTGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.RemoveUnit) - - -- Remove flight from data base. - _DATABASE.FLIGHTGROUPS[self.groupname]=nil - - + -- Call OPSGROUP function. - self:GetParent(self).onafterStop(self, From, Event, To) + self:GetParent(self).onafterStop(self, From, Event, To) + + -- Remove flight from data base. + _DATABASE.FLIGHTGROUPS[self.groupname]=nil end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index d7a5ca114..a99891e2c 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -903,7 +903,7 @@ function SQUADRON:RecruitAssets(Mission, Npayloads) end -- Check if in a state where we really do not want to fight any more. - if flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() then + if flightgroup:IsHolding() or flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() or flightgroup:IsStopped() then combatready=false end From edb0032118e19896348822ec88069545efa37d81 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 8 Sep 2020 18:04:03 +0200 Subject: [PATCH 70/79] ops --- Moose Development/Moose/Ops/OpsGroup.lua | 73 ++++++++++++++++++++++-- 1 file changed, 69 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 0af2da08b..c57f46d75 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -234,7 +234,15 @@ OPSGROUP.TaskType={ -- @field #number Alarm Alarm state. -- @field #number Formation Formation. -- @field #boolean EPLRS data link. --- @field #boolean Disperse Disperse under fire. +-- @field #boolean Disperse Disperse under fire. + + +--- Weapon range data. +-- @type OPSGROUP.WeaponData +-- @field #number BitType Type of weapon. +-- @field #number RangeMin Min range in meters. +-- @field #number RangeMax Max range in meters. +-- @field #number ReloadTime Time to reload in seconds. --- Ammo data. -- @type OPSGROUP.Ammo @@ -470,6 +478,47 @@ function OPSGROUP:AddCheckZone(CheckZone) return self end + +--- Add a zone that triggers and event if the group enters or leaves any of the zones. +-- @param #OPSGROUP self +-- @param #number RangeMin +-- @param #number RangeMax +-- @param #number BitType +-- @return #OPSGROUP self +function OPSGROUP:AddWeaponRange(RangeMin, RangeMax, BitType) + + RangeMin=(RangeMin or 0)*1000 + RangeMax=(RangeMax or 10)*1000 + + local weapon={} --#OPSGROUP.WeaponData + + weapon.BitType=BitType or ENUMS.WeaponFlag.Auto + weapon.RangeMax=RangeMax + weapon.RangeMin=RangeMin + + self.weaponData=self.weaponData or {} + self.weaponData[weapon.BitType]=weapon + + return self +end + +--- +-- @param #OPSGROUP self +-- @param #number BitType +-- @return #OPSGROUP.WeaponData Weapon range data. +function OPSGROUP:GetWeaponData(BitType) + + BitType=BitType or ENUMS.WeaponFlag.Auto + + if self.weaponData[BitType] then + return self.wself.weaponData[BitType] + else + return self.wself.weaponData[ENUMS.WeaponFlag.Auto] + end + +end + + --- Get set of detected units. -- @param #OPSGROUP self -- @return Core.Set#SET_UNIT Set of detected units. @@ -2400,9 +2449,6 @@ function OPSGROUP:RouteToMission(mission, delay) -- Speed to mission waypoint. local SpeedToMission=UTILS.KmphToKnots(self.speedCruise) - - -- Add waypoint. - local waypoint=self:AddWaypoint(waypointcoord, SpeedToMission, nil, nil, false) -- Special for Troop transport. if mission.type==AUFTRAG.Type.TROOPTRANSPORT then @@ -2421,7 +2467,26 @@ function OPSGROUP:RouteToMission(mission, delay) end + elseif mission.type==AUFTRAG.Type.ARTY then + + local weapondata=self:GetWeaponData(mission.engageWeaponType) + + local targetcoord=mission:GetTargetCoordinate() + + local heading=self:GetCoordinate():HeadingTo(targetcoord) + + local dist=self:GetCoordinate():Get2DDistance(targetcoord) + + if dist>weapondata.RangeMax then + waypointcoord=self:GetCoordinate():Translate(dist-weapondata.RangeMax, heading) + elseif dist Date: Wed, 9 Sep 2020 00:02:46 +0200 Subject: [PATCH 71/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 2 ++ Moose Development/Moose/Ops/OpsGroup.lua | 41 ++++++++++++++++------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 5af542aaf..eebcc594c 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -69,6 +69,8 @@ function ARMYGROUP:New(GroupName) self:SetDefaultAlarmstate() self:SetDetection() self:SetPatrolAdInfinitum(false) + + --self:AddWeaponRange(10, 32) -- Add FSM transitions. -- From State --> Event --> To State diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index c57f46d75..ee97086be 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -511,9 +511,9 @@ function OPSGROUP:GetWeaponData(BitType) BitType=BitType or ENUMS.WeaponFlag.Auto if self.weaponData[BitType] then - return self.wself.weaponData[BitType] + return self.weaponData[BitType] else - return self.wself.weaponData[ENUMS.WeaponFlag.Auto] + return self.weaponData[ENUMS.WeaponFlag.Auto] end end @@ -2469,18 +2469,35 @@ function OPSGROUP:RouteToMission(mission, delay) elseif mission.type==AUFTRAG.Type.ARTY then + -- Get weapon range. local weapondata=self:GetWeaponData(mission.engageWeaponType) - local targetcoord=mission:GetTargetCoordinate() + if weapondata then - local heading=self:GetCoordinate():HeadingTo(targetcoord) - - local dist=self:GetCoordinate():Get2DDistance(targetcoord) - - if dist>weapondata.RangeMax then - waypointcoord=self:GetCoordinate():Translate(dist-weapondata.RangeMax, heading) - elseif distweapondata.RangeMax then + local d=dist-weapondata.RangeMax + d=(1.1)*d + + -- New waypoint coord. + waypointcoord=self:GetCoordinate():Translate(d, heading) + elseif dist=5*60 then + if holdtime>=10*60 then self:E(self.lid..string.format("WARNING: Group came to an unexpected standstill. Speed=%.1f<%.1f m/s expected for %d sec", speed, ExpectedSpeed, holdtime)) From 717842b2767a33c2e3f959f523969024212e16ce Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 10 Sep 2020 00:34:17 +0200 Subject: [PATCH 72/79] Ops --- Moose Development/Moose/Core/Timer.lua | 15 ++-- .../Moose/Functional/Warehouse.lua | 82 ++++++++++--------- Moose Development/Moose/Ops/AirWing.lua | 1 - Moose Development/Moose/Ops/Airboss.lua | 53 ++++++++++++ Moose Development/Moose/Ops/FlightGroup.lua | 6 +- Moose Development/Moose/Ops/OpsGroup.lua | 8 +- 6 files changed, 110 insertions(+), 55 deletions(-) diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 8154bcc5a..26f809c1d 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -11,7 +11,7 @@ -- -- ### Author: **funkyfranky** -- @module Core.Timer --- @image CORE_Timer.png +-- @image Core_Scheduler.JPG --- TIMER class. @@ -19,6 +19,7 @@ -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. -- @field #number tid Timer ID returned by the DCS API function. +-- @field #number uid Unique ID of the timer. -- @field #function func Timer function. -- @field #table para Parameters passed to the timer function. -- @field #number Tstart Relative start time in seconds. @@ -28,7 +29,7 @@ -- @field #number ncallsMax Max number of function calls. If reached, timer is stopped. -- @extends Core.Base#BASE ---- *Better three hours too soon than a minute too late.* – William Shakespeare +--- *Better three hours too soon than a minute too late.* - William Shakespeare -- -- === -- @@ -36,7 +37,7 @@ -- -- # The TIMER Concept -- --- The TIMER class is the little sister of the SCHEDULER class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. +-- The TIMER class is the little sister of the @{Core.Scheduler#SCHEDULER} class. It does the same thing but is a bit easier to use and has less overhead. It should be sufficient in many cases. -- -- It provides an easy interface to the DCS [timer.scheduleFunction](https://wiki.hoggitworld.com/view/DCS_func_scheduleFunction). -- @@ -62,20 +63,20 @@ -- -- Note that -- --- * if *Tstart* is not specified (*nil*), the first function call happens immediately. +-- * if *Tstart* is not specified (*nil*), the first function call happens immediately, i.e. after one millisecond. -- * if *dT* is not specified (*nil*), the function is called only once. -- * if *Duration* is not specified (*nil*), the timer runs forever or until stopped manually or until the max function calls are reached (see below). -- -- For example, -- -- mytimer:Start(3) -- Will call the function once after 3 seconds. --- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternaty. +-- mytimer:Start(nil, 0.5) -- Will call right now and then every 0.5 sec until all eternity. -- mytimer:Start(nil, 2.0, 20) -- Will call right now and then every 2.0 sec for 20 sec. -- mytimer:Start(1.0, nil, 10) -- Does not make sense as the function is only called once anyway. -- -- ## Stopping the Timer -- --- The timer can be stopped manually by the @{#TIMER.Start}(*Delay*) function +-- The timer can be stopped manually by the @{#TIMER.Stop}(*Delay*) function -- -- mytimer:Stop() -- @@ -206,7 +207,7 @@ function TIMER:Stop(Delay) if self.tid then -- Remove timer function. - self:I(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) + self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) timer.removeFunction(self.tid) -- Remove DB entry. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index bf027907c..3ce84ee33 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1552,7 +1552,7 @@ WAREHOUSE = { ClassName = "WAREHOUSE", Debug = false, - verbosity = 0, + verbosity = 2, lid = nil, Report = true, warehouse = nil, @@ -1893,7 +1893,7 @@ function WAREHOUSE:New(warehouse, alias) -- Defaults self:SetMarker(true) self:SetReportOff() - self:SetVerbosityLevel(0) + --self:SetVerbosityLevel(0) -- Add warehouse to database. _WAREHOUSEDB.Warehouses[self.uid]=self @@ -3881,7 +3881,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu self:_DebugMessage(string.format("Removing group %s", group:GetName()), 5) -- Setting parameter to false, i.e. creating NO dead or remove unit event, seems to not confuse the dispatcher logic. -- TODO: It would be nice, however, to have the remove event. - group:Destroy(false) + group:Destroy() --(false) end else @@ -5158,7 +5158,7 @@ end -- @param #WAREHOUSE.Pendingitem request The request of the dead asset. function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local text=string.format("Asset %s from request id=%d was spawned!", asset.spawngroupname, request.uid) - self:T(self.lid..text) + self:I(self.lid..text) -- Sete asset state to spawned. asset.spawned=true @@ -5169,22 +5169,22 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local assetitem=_asset --#WAREHOUSE.Assetitem -- Debug info. - self:T(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) + self:I(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) if assetitem.spawned then n=n+1 else - self:T(self.lid.."FF What?! This should not happen!") + self:I(self.lid.."FF What?! This should not happen!") end end -- Trigger event. if n==request.nasset+request.ntransport then - self:T3(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned", n, request.nasset, request.ntransport, request.uid)) + self:I(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned", n, request.nasset, request.ntransport, request.uid)) self:RequestSpawned(request, request.cargogroupset, request.transportgroupset) else - self:T3(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET", n, request.nasset, request.ntransport, request.uid)) + self:I(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET", n, request.nasset, request.ntransport, request.uid)) end end @@ -6119,36 +6119,44 @@ function WAREHOUSE:_OnEventBirth(EventData) -- Get asset and request from id. local asset=self:GetAssetByID(aid) local request=self:GetRequestByID(rid) - - -- Debug message. - self:T(self.lid..string.format("Warehouse %s captured event birth of its asset unit %s. spawned=%s", self.alias, EventData.IniUnitName, tostring(asset.spawned))) - - -- Birth is triggered for each unit. We need to make sure not to call this too often! - if not asset.spawned then - - -- Remove asset from stock. - self:_DeleteStockItem(asset) - - -- Set spawned switch. - asset.spawned=true - asset.spawngroupname=group:GetName() - - -- Add group. - if asset.iscargo==true then - request.cargogroupset=request.cargogroupset or SET_GROUP:New() - request.cargogroupset:AddGroup(group) - else - request.transportgroupset=request.transportgroupset or SET_GROUP:New() - request.transportgroupset:AddGroup(group) + + if asset and request then + + + -- Debug message. + self:I(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s", self.alias, request.uid, asset.uid, EventData.IniUnitName, tostring(asset.spawned))) + + -- Birth is triggered for each unit. We need to make sure not to call this too often! + if not asset.spawned then + + -- Remove asset from stock. + self:_DeleteStockItem(asset) + + -- Set spawned switch. + asset.spawned=true + asset.spawngroupname=group:GetName() + + -- Add group. + if asset.iscargo==true then + request.cargogroupset=request.cargogroupset or SET_GROUP:New() + request.cargogroupset:AddGroup(group) + else + request.transportgroupset=request.transportgroupset or SET_GROUP:New() + request.transportgroupset:AddGroup(group) + end + + -- Set warehouse state. + group:SetState(group, "WAREHOUSE", self) + + -- Asset spawned FSM function. + --self:__AssetSpawned(1, group, asset, request) + env.info(string.format("FF asset spawned %s, %s", asset.spawngroupname, EventData.IniUnitName)) + self:AssetSpawned(group, asset, request) + end - - -- Set warehouse state. - group:SetState(group, "WAREHOUSE", self) - - -- Asset spawned FSM function. - --self:__AssetSpawned(1, group, asset, request) - self:AssetSpawned(group, asset, request) - + + else + self:E(self.lid..string.format("ERROR: Either asset AID=%s or request RID=%s are nil in event birth of unit %s", tostring(aid), tostring(rid), tostring(EventData.IniUnitName))) end else diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index a2c2e2fd4..9a768dd73 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -829,7 +829,6 @@ function AIRWING:onafterStatus(From, Event, To) ------------------ if self.verbose>=2 then local text=string.format("Missions Total=%d:", #self.missionqueue) - env.info("FF verbose "..self.verbose) for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index feb293680..c0009886d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3384,6 +3384,7 @@ function AIRBOSS:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Ejection) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) self:HandleEvent(EVENTS.MissionEnd) + self:HandleEvent(EVENTS.RemoveUnit) --self.StatusScheduler=SCHEDULER:New(self) --self.StatusScheduler:Schedule(self, self._Status, {}, 1, 0.5) @@ -8833,6 +8834,58 @@ function AIRBOSS:OnEventEjection(EventData) end +--- Airboss event handler for event REMOVEUNIT. +-- @param #AIRBOSS self +-- @param Core.Event#EVENTDATA EventData +function AIRBOSS:OnEventRemoveUnit(EventData) + self:F3({eventland = EventData}) + + -- Nil checks. + if EventData==nil then + self:E(self.lid.."ERROR: EventData=nil in event REMOVEUNIT!") + self:E(EventData) + return + end + if EventData.IniUnit==nil then + self:E(self.lid.."ERROR: EventData.IniUnit=nil in event REMOVEUNIT!") + self:E(EventData) + return + end + + + local _unitName=EventData.IniUnitName + local _unit, _playername=self:_GetPlayerUnitAndName(_unitName) + + self:T3(self.lid.."EJECT: unit = "..tostring(EventData.IniUnitName)) + self:T3(self.lid.."EJECT: group = "..tostring(EventData.IniGroupName)) + self:T3(self.lid.."EJECT: player = "..tostring(_playername)) + + if _unit and _playername then + self:T(self.lid..string.format("Player %s removed!",_playername)) + + -- Get player flight. + local flight=self.players[_playername] + + -- Remove flight completely from all queues and collapse marshal if necessary. + if flight then + self:_RemoveFlight(flight, true) + end + + else + -- Debug message. + self:T(self.lid..string.format("AI unit %s removed!", EventData.IniUnitName)) + + -- Remove element/unit from flight group and from all queues if no elements alive. + self:_RemoveUnitFromFlight(EventData.IniUnit) + + -- What could happen is, that another element has landed (recovered) already and this one crashes. + -- This would mean that the flight would not be deleted from the queue ==> Check if section recovered. + local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) + self:_CheckSectionRecovered(flight) + end + +end + --- Airboss event handler for event player leave unit. -- @param #AIRBOSS self -- @param Core.Event#EVENTDATA EventData diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 8bb75be4f..f708d2c86 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2565,17 +2565,15 @@ function FLIGHTGROUP:_InitGroup() -- Set default radio. self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) - --TODO callsign from template or getCallsign + -- Set callsign. local callsign=self.template.units[1].callsign - env.info("FF callsign:",showMessageBox) - if type(callsign)=="number" then + if type(callsign)=="number" then -- Sometimes callsign is just "101". local cs=tostring(callsign) callsign={} callsign[1]=cs:sub(1,1) callsign[2]=cs:sub(2,2) callsign[3]=cs:sub(3,3) end - self:I({callsign=callsign}) self.callsign.NumberSquad=callsign[1] self.callsign.NumberGroup=callsign[2] self.callsign.NumberElement=callsign[3] -- First element only diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ee97086be..37b4d5dcd 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1919,7 +1919,6 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) else local text=string.format("WARNING: No (current) task to cancel!") - MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:E(self.lid..text) end @@ -2211,7 +2210,6 @@ function OPSGROUP:onafterMissionStart(From, Event, To, Mission) -- Debug output. local text=string.format("Starting %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName()) self:T(self.lid..text) - MESSAGE:New(text, 30, self.groupname):ToAllIf(self.Debug) -- Set current mission. self.currentmission=Mission.auftragsnummer @@ -2237,7 +2235,6 @@ function OPSGROUP:onafterMissionExecute(From, Event, To, Mission) local text=string.format("Executing %s Mission %s, target %s", Mission.type, tostring(Mission.name), Mission:GetTargetName()) self:T(self.lid..text) - MESSAGE:New(text, 30, self.groupname):ToAllIf(self.Debug) -- Set group mission status to EXECUTING. Mission:SetGroupStatus(self, AUFTRAG.GroupStatus.EXECUTING) @@ -3454,7 +3451,7 @@ function OPSGROUP:SwitchROE(roe) self.group:OptionROE(self.option.ROE) - self:I(self.lid..string.format("Setting current ROE=%d (%s)", self.option.ROE, self:_GetROEName(self.option.ROE))) + self:T(self.lid..string.format("Setting current ROE=%d (%s)", self.option.ROE, self:_GetROEName(self.option.ROE))) end @@ -3893,7 +3890,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) else - self:I(self.lid.."INFO: Current radio not switched as freq/modulation did not change") + self:T(self.lid.."INFO: Current radio not switched as freq/modulation did not change") end else @@ -4573,7 +4570,6 @@ function OPSGROUP:GetAmmoUnit(unit, display) else self:T3(self.lid..text) end - MESSAGE:New(text, 10):ToAllIf(display) -- Total amount of ammunition. nammo=nshells+nrockets+nmissiles+nbombs+ntorps From a00c198bccdbeebf83496b1111c2f89faf087a23 Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 15 Sep 2020 01:13:11 +0200 Subject: [PATCH 73/79] Update Warehouse.lua - Added born check. Helps with delayed spawning on carriers. --- .../Moose/Functional/Warehouse.lua | 331 +++++++++--------- 1 file changed, 168 insertions(+), 163 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3ce84ee33..e9661a754 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1552,7 +1552,7 @@ WAREHOUSE = { ClassName = "WAREHOUSE", Debug = false, - verbosity = 2, + verbosity = 0, lid = nil, Report = true, warehouse = nil, @@ -3472,172 +3472,174 @@ function WAREHOUSE:_JobDone() -- Loop over all pending requests of this warehouse. for _,request in pairs(self.pending) do local request=request --#WAREHOUSE.Pendingitem + + if request.born then - -- Count number of cargo groups. - local ncargo=0 - if request.cargogroupset then - ncargo=request.cargogroupset:Count() - end - - -- Count number of transport groups (if any). - local ntransport=0 - if request.transportgroupset then - ntransport=request.transportgroupset:Count() - end - - local ncargotot=request.nasset - local ncargodelivered=request.ndelivered - - -- Dead cargo: Ndead=Ntot-Ndeliverd-Nalive, - local ncargodead=ncargotot-ncargodelivered-ncargo - - - local ntransporttot=request.ntransport - local ntransporthome=request.ntransporthome - - -- Dead transport: Ndead=Ntot-Nhome-Nalive. - local ntransportdead=ntransporttot-ntransporthome-ntransport - - local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d | Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d", - request.uid, ncargotot, ncargo, ncargodelivered, ncargodead, ntransporttot, ntransport, ntransporthome, ntransportdead) - self:T(self.lid..text) - - - -- Handle different cases depending on what asset are still around. - if ncargo==0 then - --------------------- - -- Cargo delivered -- - --------------------- - - -- Trigger delivered event. - if not self.delivered[request.uid] then - self:Delivered(request) + -- Count number of cargo groups. + local ncargo=0 + if request.cargogroupset then + ncargo=request.cargogroupset:Count() end - - -- Check if transports are back home? - if ntransport==0 then - --------------- - -- Job done! -- - --------------- - - -- Info on job. - if self.verbosity>=1 then - local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n", self.alias, request.uid, request.warehouse.alias) - text=text..string.format("- %d of %d assets delivered. Casualties %d.", ncargodelivered, ncargotot, ncargodead) - if request.ntransport>0 then - text=text..string.format("\n- %d of %d transports returned home. Casualties %d.", ntransporthome, ntransporttot, ntransportdead) - end - self:_InfoMessage(text, 20) + + -- Count number of transport groups (if any). + local ntransport=0 + if request.transportgroupset then + ntransport=request.transportgroupset:Count() + end + + local ncargotot=request.nasset + local ncargodelivered=request.ndelivered + + -- Dead cargo: Ndead=Ntot-Ndeliverd-Nalive, + local ncargodead=ncargotot-ncargodelivered-ncargo + + + local ntransporttot=request.ntransport + local ntransporthome=request.ntransporthome + + -- Dead transport: Ndead=Ntot-Nhome-Nalive. + local ntransportdead=ntransporttot-ntransporthome-ntransport + + local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d | Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d", + request.uid, ncargotot, ncargo, ncargodelivered, ncargodead, ntransporttot, ntransport, ntransporthome, ntransportdead) + self:T(self.lid..text) + + + -- Handle different cases depending on what asset are still around. + if ncargo==0 then + --------------------- + -- Cargo delivered -- + --------------------- + + -- Trigger delivered event. + if not self.delivered[request.uid] then + self:Delivered(request) end - - -- Mark request for deletion. - table.insert(done, request) - - else - ----------------------------------- - -- No cargo but still transports -- - ----------------------------------- - - -- This is difficult! How do I know if transports were unused? They could also be just on their way back home. - -- ==> Need to do a lot of checks. - - -- All transports are dead but there is still cargo left ==> Put cargo back into stock. - for _,_group in pairs(request.transportgroupset:GetSetObjects()) do - local group=_group --Wrapper.Group#GROUP - - -- Check if group is alive. - if group and group:IsAlive() then - - -- Check if group is in the spawn zone? - local category=group:GetCategory() - - -- Get current speed. - local speed=group:GetVelocityKMH() - local notmoving=speed<1 - - -- Closest airbase. - local airbase=group:GetCoordinate():GetClosestAirbase():GetName() - local athomebase=self.airbase and self.airbase:GetName()==airbase - - -- On ground - local onground=not group:InAir() - - -- In spawn zone. - local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone) - - -- Check conditions for being back home. - local ishome=false - if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then - -- Units go back to the spawn zone, helicopters land and they should not move any more. - ishome=inspawnzone and onground and notmoving - elseif category==Group.Category.AIRPLANE then - -- Planes need to be on ground at their home airbase and should not move any more. - ishome=athomebase and onground and notmoving + + -- Check if transports are back home? + if ntransport==0 then + --------------- + -- Job done! -- + --------------- + + -- Info on job. + if self.verbosity>=1 then + local text=string.format("Warehouse %s: Job on request id=%d for warehouse %s done!\n", self.alias, request.uid, request.warehouse.alias) + text=text..string.format("- %d of %d assets delivered. Casualties %d.", ncargodelivered, ncargotot, ncargodead) + if request.ntransport>0 then + text=text..string.format("\n- %d of %d transports returned home. Casualties %d.", ntransporthome, ntransporttot, ntransportdead) end - - -- Debug text. - local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s", group:GetName(), speed, tostring(onground), airbase, tostring(inspawnzone), tostring(ishome)) - self:T(self.lid..text) - - if ishome then - - -- Info message. - local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.", self.alias, request.uid, group:GetName()) + self:_InfoMessage(text, 20) + end + + -- Mark request for deletion. + table.insert(done, request) + + else + ----------------------------------- + -- No cargo but still transports -- + ----------------------------------- + + -- This is difficult! How do I know if transports were unused? They could also be just on their way back home. + -- ==> Need to do a lot of checks. + + -- All transports are dead but there is still cargo left ==> Put cargo back into stock. + for _,_group in pairs(request.transportgroupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + + -- Check if group is alive. + if group and group:IsAlive() then + + -- Check if group is in the spawn zone? + local category=group:GetCategory() + + -- Get current speed. + local speed=group:GetVelocityKMH() + local notmoving=speed<1 + + -- Closest airbase. + local airbase=group:GetCoordinate():GetClosestAirbase():GetName() + local athomebase=self.airbase and self.airbase:GetName()==airbase + + -- On ground + local onground=not group:InAir() + + -- In spawn zone. + local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone) + + -- Check conditions for being back home. + local ishome=false + if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then + -- Units go back to the spawn zone, helicopters land and they should not move any more. + ishome=inspawnzone and onground and notmoving + elseif category==Group.Category.AIRPLANE then + -- Planes need to be on ground at their home airbase and should not move any more. + ishome=athomebase and onground and notmoving + end + + -- Debug text. + local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s", group:GetName(), speed, tostring(onground), airbase, tostring(inspawnzone), tostring(ishome)) self:T(self.lid..text) - - -- Debug smoke. - if self.Debug then - group:SmokeRed() + + if ishome then + + -- Info message. + local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.", self.alias, request.uid, group:GetName()) + self:T(self.lid..text) + + -- Debug smoke. + if self.Debug then + group:SmokeRed() + end + + -- Group arrived. + self:Arrived(group) end - - -- Group arrived. - self:Arrived(group) end end + end - - end - - else - - if ntransport==0 and request.ntransport>0 then - ----------------------------------- - -- Still cargo but no transports -- - ----------------------------------- - - local ncargoalive=0 - - -- All transports are dead but there is still cargo left ==> Put cargo back into stock. - for _,_group in pairs(request.cargogroupset:GetSetObjects()) do - --local group=group --Wrapper.Group#GROUP - - -- These groups have been respawned as cargo, i.e. their name changed! - local groupname=_group:GetName() - local group=GROUP:FindByName(groupname.."#CARGO") - - -- Check if group is alive. - if group and group:IsAlive() then - - -- Check if group is in spawn zone? - if group:IsPartlyOrCompletelyInZone(self.spawnzone) then - -- Debug smoke. - if self.Debug then - group:SmokeBlue() + + else + + if ntransport==0 and request.ntransport>0 then + ----------------------------------- + -- Still cargo but no transports -- + ----------------------------------- + + local ncargoalive=0 + + -- All transports are dead but there is still cargo left ==> Put cargo back into stock. + for _,_group in pairs(request.cargogroupset:GetSetObjects()) do + --local group=group --Wrapper.Group#GROUP + + -- These groups have been respawned as cargo, i.e. their name changed! + local groupname=_group:GetName() + local group=GROUP:FindByName(groupname.."#CARGO") + + -- Check if group is alive. + if group and group:IsAlive() then + + -- Check if group is in spawn zone? + if group:IsPartlyOrCompletelyInZone(self.spawnzone) then + -- Debug smoke. + if self.Debug then + group:SmokeBlue() + end + -- Add asset group back to stock. + self:AddAsset(group) + ncargoalive=ncargoalive+1 end - -- Add asset group back to stock. - self:AddAsset(group) - ncargoalive=ncargoalive+1 end + end - + + -- Info message. + self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!", self.alias, request.uid, ncargoalive)) end - - -- Info message. - self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!", self.alias, request.uid, ncargoalive)) + end - - end - + end -- born check end -- loop over requests -- Remove pending requests if done. @@ -5158,7 +5160,7 @@ end -- @param #WAREHOUSE.Pendingitem request The request of the dead asset. function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local text=string.format("Asset %s from request id=%d was spawned!", asset.spawngroupname, request.uid) - self:I(self.lid..text) + self:T(self.lid..text) -- Sete asset state to spawned. asset.spawned=true @@ -5169,22 +5171,23 @@ function WAREHOUSE:onafterAssetSpawned(From, Event, To, group, asset, request) local assetitem=_asset --#WAREHOUSE.Assetitem -- Debug info. - self:I(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) + self:T(self.lid..string.format("Asset %s spawned %s as %s", assetitem.templatename, tostring(assetitem.spawned), tostring(assetitem.spawngroupname))) if assetitem.spawned then n=n+1 else - self:I(self.lid.."FF What?! This should not happen!") + -- Now this can happend if multiple groups need to be spawned in one request. + --self:I(self.lid.."FF What?! This should not happen!") end end -- Trigger event. if n==request.nasset+request.ntransport then - self:I(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned", n, request.nasset, request.ntransport, request.uid)) + self:T(self.lid..string.format("All assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned. Calling RequestSpawned", n, request.nasset, request.ntransport, request.uid)) self:RequestSpawned(request, request.cargogroupset, request.transportgroupset) else - self:I(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET", n, request.nasset, request.ntransport, request.uid)) + self:T(self.lid..string.format("Not all assets %d (ncargo=%d + ntransport=%d) of request rid=%d spawned YET", n, request.nasset, request.ntransport, request.uid)) end end @@ -5795,7 +5798,7 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) template.lateActivation=false if asset.missionTask then - self:I(self.lid..string.format("Setting mission task to %s", tostring(asset.missionTask))) + self:T(self.lid..string.format("Setting mission task to %s", tostring(asset.missionTask))) template.task=asset.missionTask end @@ -6121,10 +6124,12 @@ function WAREHOUSE:_OnEventBirth(EventData) local request=self:GetRequestByID(rid) if asset and request then - - + -- Debug message. - self:I(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s", self.alias, request.uid, asset.uid, EventData.IniUnitName, tostring(asset.spawned))) + self:T(self.lid..string.format("Warehouse %s captured event birth of request ID=%d, asset ID=%d, unit %s spawned=%s", self.alias, request.uid, asset.uid, EventData.IniUnitName, tostring(asset.spawned))) + + -- Set born to true. + request.born=true -- Birth is triggered for each unit. We need to make sure not to call this too often! if not asset.spawned then @@ -6150,7 +6155,7 @@ function WAREHOUSE:_OnEventBirth(EventData) -- Asset spawned FSM function. --self:__AssetSpawned(1, group, asset, request) - env.info(string.format("FF asset spawned %s, %s", asset.spawngroupname, EventData.IniUnitName)) + --env.info(string.format("FF asset spawned %s, %s", asset.spawngroupname, EventData.IniUnitName)) self:AssetSpawned(group, asset, request) end From 40ffef035bd63790baaca44f09531e531ef0c1be Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 15 Sep 2020 18:11:19 +0200 Subject: [PATCH 74/79] Ops --- Moose Development/Moose/Ops/Auftrag.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index a98f3b52c..dcd5e46ed 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -120,7 +120,7 @@ -- -- @extends Core.Fsm#FSM ---- *A warrior's mission is to foster the success of others.* --- Morihei Ueshiba +--- *A warrior's mission is to foster the success of others.* - Morihei Ueshiba -- -- === -- @@ -128,10 +128,10 @@ -- -- # The AUFTRAG Concept -- --- As you probably know, setting tasks in DCS is often tedious. The AUFTRAG class significantly simplifies the necessary workflow by using optimized default parameters. +-- The AUFTRAG class significantly simplifies the workflow of using DCS tasks. -- -- You can think of an AUFTRAG as document, which contains the mission briefing, i.e. information about the target location, mission altitude, speed and various other parameters. --- This document can be handed over directly to a pilot (or multiple pilots) via the FLIGHTGROUP class. The pilots will then execute the mission. +-- This document can be handed over directly to a pilot (or multiple pilots) via the @{Ops.FlightGroup#FLIGHTGROUP} class. The pilots will then execute the mission. -- The AUFTRAG document can also be given to an AIRWING. The airwing will then determine the best assets (pilots and payloads) available for the job. -- One more up the food chain, an AUFTRAG can be passed to a WINGCOMMANDER. The wing commander will find the best AIRWING and pass the job over to it. -- From 29d694722b82349778c790dd5770ef8f9e73ef6d Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 16 Sep 2020 23:35:13 +0200 Subject: [PATCH 75/79] Ops - Quite a few fixes while going through the example missions. --- Moose Development/Moose/Ops/Auftrag.lua | 30 +--- Moose Development/Moose/Ops/FlightGroup.lua | 82 ++++++--- Moose Development/Moose/Ops/NavyGroup.lua | 3 +- Moose Development/Moose/Ops/OpsGroup.lua | 175 +++++++++++++------- 4 files changed, 187 insertions(+), 103 deletions(-) diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index dcd5e46ed..3bffa6b66 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -1169,7 +1169,10 @@ function AUFTRAG:NewARTY(Target, Nshots, Radius) mission.optionROE=ENUMS.ROE.OpenFire -- Ground/naval need open fire! mission.optionAlarm=0 - mission.missionFraction=0.1 + mission.missionFraction=0.0 + + -- Evaluate after 8 min. + mission.dTevaluate=8*60 mission.DCStask=mission:GetDCSMissionTask() @@ -1185,30 +1188,11 @@ function AUFTRAG:NewTargetAir(Target) local mission=nil --#AUFTRAG self.engageTarget=Target - - --[[ - if Target.category==TARGET.Category.GROUND then - - - elseif Target.category==TARGET.Category.AIRCRAFT then - - mission=AUFTRAG:NewINTERCEPT(Target) - - elseif Target.category==TARGET.Category.AIRBASE then - - mission=AUFTRAG:NewBOMBRUNWAY(Airdrome,Altitude) - - elseif Target.category==TARGET.Category.COORDINATE then - - - end - ]] - + local target=self.engageTarget:GetObject() local mission=self:NewAUTO(target) - if mission then mission:SetPriority(10, true) end @@ -2133,6 +2117,9 @@ function AUFTRAG:Evaluate() local Ntargets=self:CountMissionTargets() local Ntargets0=self:GetTargetInitialNumber() + local Life=self:GetTargetLife() + local Life0=self:GetTargetInitialLife() + if Ntargets0>0 then @@ -2196,6 +2183,7 @@ function AUFTRAG:Evaluate() text=text..string.format("Own losses = %.1f %%\n", owndamage) text=text..string.format("--------------------------\n") text=text..string.format("Targets left = %d/%d\n", Ntargets, Ntargets0) + text=text..string.format("Targets life = %.1f/%.1f\n", Life, Life0) text=text..string.format("Enemy losses = %.1f %%\n", targetdamage) text=text..string.format("--------------------------\n") --text=text..string.format("Loss ratio = %.1f %%\n", targetdamage) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f708d2c86..025fdb905 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -708,6 +708,41 @@ end -- @param #FLIHGTGROUP self function FLIGHTGROUP:onbeforeStatus(From, Event, To) + -- First we check if elements are still alive. Could be that they were despawned without notice, e.g. when landing on a too small airbase. + for i,_element in pairs(self.elements) do + local element=_element --#FLIGHTGROUP.Element + + -- Check that element is not already dead or not yet alive. + if element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then + + -- Unit shortcut. + local unit=element.unit + + local isdead=false + if unit and unit:IsAlive() then + + -- Get life points. + local life=unit:GetLife() or 0 + + -- Units with life <=1 are dead. + if life<=1 then + isdead=true + end + + else + -- Not alive any more. + isdead=true + end + + -- This one is dead. + if isdead then + self:E(self.lid..string.format("Element %s is dead! Probably despawned without notice or landed at a too small airbase", tostring(element.name))) + self:ElementDead(element) + end + + end + end + if self:IsDead() then self:T(self.lid..string.format("Onbefore Status DEAD ==> false")) return false @@ -1327,18 +1362,27 @@ end -- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) self:T2(self.lid..string.format("Element landed %s at %s airbase", Element.name, airbase and airbase:GetName() or "unknown")) + + if self.despawnAfterLanding then + + -- Despawn the element. + self:DespawnElement(Element) + + else - -- Helos with skids land directly on parking spots. - if self.ishelo then - - local Spot=self:GetParkingSpot(Element, 10, airbase) - - self:_SetElementParkingAt(Element, Spot) - + -- Helos with skids land directly on parking spots. + if self.ishelo then + + local Spot=self:GetParkingSpot(Element, 10, airbase) + + self:_SetElementParkingAt(Element, Spot) + + end + + -- Set element status. + self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) + end - - -- Set element status. - self:_UpdateStatus(Element, OPSGROUP.ElementStatus.LANDED, airbase) end --- On after "ElementArrived" event. @@ -1410,6 +1454,9 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- Set ROT. self:SwitchROT(self.option.ROT) + + -- Set Formation + self:SwitchFormation(self.option.Formation) -- Turn TACAN beacon on. if self.tacan.On then @@ -1572,10 +1619,6 @@ function FLIGHTGROUP:onafterLanded(From, Event, To, airbase) -- Add flight to taxiinb queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAXIINB) end - - if self.despawnAfterLanding then - self:Despawn() - end end @@ -1603,9 +1646,9 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end - -- Stop and despawn in 5 min. + -- Despawn in 5 min. if not self.airwing then - self:__Stop(5*60) + self:Despawn(5*60) end end @@ -1731,9 +1774,11 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. local current=self.group:GetCoordinate():WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speed, true, nil, {}, "Current") table.insert(wp, current) + + local Nwp=self.waypoints and #self.waypoints or 0 -- Add remaining waypoints to route. - for i=n, #self.waypoints do + for i=n, Nwp do table.insert(wp, self.waypoints[i]) end @@ -2437,9 +2482,6 @@ function FLIGHTGROUP:onafterStop(From, Event, To) end end - -- Destroy group. No event is generated. - -- DISABLED for now. Should use :Despawn() or :Destroy() which then calls stop. - --self.group:Destroy(false) end -- Handle events: diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 7ae587da8..a8d1b91e7 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -575,7 +575,7 @@ function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth) n=n or self:GetWaypointIndexNext() -- Debug info. - self:T(self.lid..string.format("FF Update route n=%d", n)) + self:T(self.lid..string.format("Update route n=%d", n)) -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. self:_UpdateWaypointTasks(n) @@ -784,6 +784,7 @@ end -- @param #string Event Event. -- @param #string To To state. function NAVYGROUP:onafterFullStop(From, Event, To) + self:T(self.lid.."Full stop ==> holding") -- Get current position. local pos=self:GetCoordinate() diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 37b4d5dcd..800ca42bf 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -669,16 +669,37 @@ function OPSGROUP:Destroy(Delay) return self end ---- Despawn a unit. +--- Despawn an element/unit of the group. -- @param #OPSGROUP self +-- @param #OPSGROUP.Element Element The element that will be despawned. +-- @param #number Delay Delay in seconds before the element will be despawned. Default immediately. +-- @param #boolean NoEventRemoveUnit If true, no event "Remove Unit" is generated. -- @return #OPSGROUP self -function OPSGROUP:DespawnUnit(UnitName) +function OPSGROUP:DespawnElement(Element, Delay, NoEventRemoveUnit) - local DCSGroup=self:GetDCSGroup() + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.DespawnElement, self, Element, 0, NoEventRemoveUnit) + else + + if Element then + + -- Get DCS unit object. + local DCSunit=Unit.getByName(Element.name) - if DCSGroup then - DCSGroup:destroy() - self:CreateEventRemoveUnit(timer.getTime(), DCSObject) + if DCSunit then + + -- Destroy object. + DCSunit:destroy() + + -- Create a remove unit event. + if not NoEventRemoveUnit then + self:CreateEventRemoveUnit(timer.getTime(), DCSunit) + end + + end + + end + end return self @@ -1082,7 +1103,7 @@ end function OPSGROUP:GetWaypointIndex(uid) if uid then - for i,_waypoint in pairs(self.waypoints) do + for i,_waypoint in pairs(self.waypoints or {}) do local waypoint=_waypoint --#OPSGROUP.Waypoint if waypoint.uid==uid then return i @@ -2241,12 +2262,7 @@ function OPSGROUP:onafterMissionExecute(From, Event, To, Mission) -- Set mission status to EXECUTING. Mission:Executing() - - -- Formation - if Mission.optionFormation then - self:SwitchFormation(Mission.optionFormation) - end - + end --- On after "PauseMission" event. @@ -2371,7 +2387,7 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) -- Remove mission waypoint. local wpidx=Mission:GetGroupWaypointIndex(self) if wpidx then - self:RemoveWaypoint(wpidx) + self:RemoveWaypointByID(wpidx) end -- Decrease patrol data. @@ -2380,6 +2396,27 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) AIRWING.UpdatePatrolPointMarker(Mission.patroldata) end + -- ROE to default. + if Mission.optionROE then + self:SwitchROE() + end + -- ROT to default + if Mission.optionROT then + self:SwitchROT() + end + -- Alarm state to default. + if Mission.optionAlarm then + self:SwitchAlarmstate() + end + -- Formation to default. + if Mission.optionFormation then + self:SwitchFormation() + end + -- Radio freq and modu to last used. + if Mission.radio and self.radioLast then + self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) + end + -- TACAN if Mission.tacan then @@ -2399,23 +2436,8 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) asset.tacan=nil end end - - -- TODO: reset mission specific parameters like radio, ROE etc. - if Mission.radio and self.radioLast then - self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) - end - if Mission.optionROE then - self:SwitchROE() - end - - if Mission.optionROT then - self:SwitchROT() - end - - if Mission.optionAlarm then - self:SwitchAlarmstate() - end + -- TODO: reset ICLS settings. -- Check if group is done. self:_CheckGroupDone(1) @@ -2482,17 +2504,21 @@ function OPSGROUP:RouteToMission(mission, delay) -- Check if we are within range. if dist>weapondata.RangeMax then - local d=dist-weapondata.RangeMax - d=(1.1)*d + + local d=(dist-weapondata.RangeMax)*1.1 -- New waypoint coord. waypointcoord=self:GetCoordinate():Translate(d, heading) + + self:T(self.lid..string.format("Out of max range = %.1f km for weapon %d", weapondata.RangeMax/1000, mission.engageWeaponType)) elseif dist Trigger "DetectedUnitKnown" event. + self:DetectedUnitKnown(Unit) + else + -- Unit is was not detected ==> Trigger "DetectedUnitNew" event. + self:DetectedUnitNew(Unit) + end + end --- On after "DetectedUnitNew" event. @@ -2747,6 +2791,9 @@ end -- @param Wrapper.Unit#UNIT Unit The detected unit. function OPSGROUP:onafterDetectedUnitNew(From, Event, To, Unit) self:T(self.lid..string.format("Detected New unit %s", Unit:GetName())) + + -- Add unit to detected unit set. + self.detectedunits:AddUnit(Unit) end --- On after "EnterZone" event. Sets self.inzones[zonename]=true. @@ -2851,9 +2898,11 @@ function OPSGROUP:onafterStop(From, Event, To) -- Stop FSM scheduler. self.CallScheduler:Clear() - if self:IsAlive() then + if self:IsAlive() and not (self:IsDead() or self:IsStopped()) then local life, life0=self:GetLifePoints() - self:E(self.lid..string.format("WARNING: Group is still alive! Life points=%d/%d. Use OPSGROUP:Destroy() or OPSGROUP:Despawn() for a clean stop", life, life0)) + local state=self:GetState() + local text=string.format("WARNING: Group is still alive! Current state=%s. Life points=%d/%d. Use OPSGROUP:Destroy() or OPSGROUP:Despawn() for a clean stop", state, life, life0) + self:E(self.lid..text) end -- Debug output. @@ -2931,6 +2980,8 @@ function OPSGROUP:_CheckDetectedUnits() local DetectedObject=Detection.object -- DCS#Object if DetectedObject and DetectedObject:isExist() and DetectedObject.id_<50000000 then + + -- Unit. local unit=UNIT:Find(DetectedObject) if unit and unit:IsAlive() then @@ -2941,17 +2992,9 @@ function OPSGROUP:_CheckDetectedUnits() -- Add unit to detected table of this run. table.insert(detected, unit) - -- Trigger detected unit event. + -- Trigger detected unit event ==> This also triggers the DetectedUnitNew and DetectedUnitKnown events. self:DetectedUnit(unit) - if self.detectedunits:FindUnit(unitname) then - -- Unit is already in the detected unit set ==> Trigger "DetectedUnitKnown" event. - self:DetectedUnitKnown(unit) - else - -- Unit is was not detected ==> Trigger "DetectedUnitNew" event. - self:DetectedUnitNew(unit) - end - end end end @@ -3059,6 +3102,8 @@ function OPSGROUP:_CheckGroupDone(delay) --- No waypoints left -- No further waypoints. Command a full stop. + self:T(self.lid..string.format("No waypoints left ==> Full Stop")) + self:__FullStop(-1) end @@ -3197,7 +3242,15 @@ function OPSGROUP:_AddWaypoint(waypoint, wpnumber) table.insert(self.waypoints, wpnumber, waypoint) -- Debug info. - self:T2(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) + self:T(self.lid..string.format("Adding waypoint at index=%d id=%d", wpnumber, waypoint.uid)) + + -- Now we obviously did not pass the final waypoint. + self.passedfinalwp=false + + -- Switch to cruise mode. + if self:IsHolding() then + self:Cruise() + end end --- Initialize Mission Editor waypoints. @@ -3288,7 +3341,7 @@ end -- @param #number n Waypoint function OPSGROUP:_UpdateWaypointTasks(n) - local waypoints=self.waypoints + local waypoints=self.waypoints or {} local nwaypoints=#waypoints for i,_wp in pairs(waypoints) do From d497bb25f3c3d825e8fd11348543545ea3c373f9 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 18 Sep 2020 18:07:10 +0200 Subject: [PATCH 76/79] Ops --- Moose Development/Moose/Core/Point.lua | 48 +++++++++++++++++++- Moose Development/Moose/Ops/Auftrag.lua | 10 ++++- Moose Development/Moose/Ops/OpsGroup.lua | 2 +- Moose Development/Moose/Ops/Target.lua | 56 +++++++++++++++++++++++- 4 files changed, 110 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8d4bf015c..6c9975ff1 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -389,7 +389,7 @@ do -- COORDINATE -- @return #boolean True if units were found. -- @return #boolean True if statics were found. -- @return #boolean True if scenery objects were found. - -- @return #table Table of MOOSE @[#Wrapper.Unit#UNIT} objects found. + -- @return #table Table of MOOSE @{Wrapper.Unit#UNIT} objects found. -- @return #table Table of DCS static objects found. -- @return #table Table of DCS scenery objects found. function COORDINATE:ScanObjects(radius, scanunits, scanstatics, scanscenery) @@ -477,7 +477,7 @@ do -- COORDINATE end for _,scenery in pairs(Scenery) do self:T(string.format("Scan found scenery %s typename=%s", scenery:getName(), scenery:getTypeName())) - SCENERY:Register(scenery:getName(), scenery) + --SCENERY:Register(scenery:getName(), scenery) end return gotunits, gotstatics, gotscenery, Units, Statics, Scenery @@ -522,7 +522,51 @@ do -- COORDINATE return umin end + + --- Scan/find SCENERY objects within a certain radius around the coordinate using the world.searchObjects() DCS API function. + -- @param #COORDINATE self + -- @param #number radius (Optional) Scan radius in meters. Default 100 m. + -- @return table Set of scenery objects. + function COORDINATE:ScanScenery(radius) + local _,_,_,_,_,scenerys=self:ScanObjects(radius, false, false, true) + + local set={} + + for _,_scenery in pairs(scenerys) do + local scenery=_scenery --DCS#Object + + local name=scenery:getName() + local s=SCENERY:Register(name, scenery) + table.insert(set, s) + + end + + return set + end + + --- Find the closest scenery to the COORDINATE within a certain radius. + -- @param #COORDINATE self + -- @param #number radius Scan radius in meters. Default 100 m. + -- @return Wrapper.Scenery#SCENERY The closest scenery or #nil if no object is inside the given radius. + function COORDINATE:FindClosestScenery(radius) + + local sceneries=self:ScanScenery(radius) + + local umin=nil --Wrapper.Scenery#SCENERY + local dmin=math.huge + for _,_scenery in pairs(sceneries) do + local scenery=_scenery --Wrapper.Scenery#SCENERY + local coordinate=scenery:GetCoordinate() + local d=self:Get2DDistance(coordinate) + if d Date: Sat, 19 Sep 2020 01:15:34 +0200 Subject: [PATCH 77/79] Ops --- Moose Development/Moose/Ops/ArmyGroup.lua | 22 +- Moose Development/Moose/Ops/FlightGroup.lua | 33 +-- Moose Development/Moose/Ops/NavyGroup.lua | 27 +- Moose Development/Moose/Ops/OpsGroup.lua | 291 +++++++++++--------- 4 files changed, 198 insertions(+), 175 deletions(-) diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index eebcc594c..94197f066 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -69,8 +69,6 @@ function ARMYGROUP:New(GroupName) self:SetDefaultAlarmstate() self:SetDetection() self:SetPatrolAdInfinitum(false) - - --self:AddWeaponRange(10, 32) -- Add FSM transitions. -- From State --> Event --> To State @@ -100,14 +98,6 @@ function ARMYGROUP:New(GroupName) -- Initialize the group. self:_InitGroup() - - -- Debug trace. - if false then - self.Debug=true - BASE:TraceOnOff(true) - BASE:TraceClass(self.ClassName) - BASE:TraceLevel(1) - end -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) @@ -342,14 +332,14 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) - -- Turn TACAN beacon on. - if self.tacan.On then - self:_SwitchTACAN(self.tacan) - end + -- Set TACAN to default. + self:_SwitchTACAN() -- Turn on the radio. - if self.radioLast then - self:SwitchRadio(self.radioLast.Freq, self.radioLst.Modu) + if self.radioDefault then + self:SwitchRadio(self.radioDefault.Freq, self.radioDefault.Modu) + else + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, true) end end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 025fdb905..81a8f4bf6 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1458,14 +1458,14 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) -- Set Formation self:SwitchFormation(self.option.Formation) - -- Turn TACAN beacon on. - if self.tacan.On then - self:_SwitchTACAN(self.tacan) - end + -- Set TACAN beacon. + self:_SwitchTACAN() - -- Turn on the radio. - if self.radioLast then - self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) + -- Set radio freq and modu. + if self.radioDefault then + self:SwitchRadio() + else + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) end -- Set callsign. @@ -2563,6 +2563,7 @@ function FLIGHTGROUP:_InitGroup() return end + -- Group object. local group=self.group --Wrapper.Group#GROUP -- Get template of group. @@ -2594,20 +2595,12 @@ function FLIGHTGROUP:_InitGroup() -- Group ammo. self.ammo=self:GetAmmoTot() - -- Radio parameters from template. + -- Radio parameters from template. Default is set on spawn if not modified by user. self.radio.Freq=tonumber(self.template.frequency) self.radio.Modu=tonumber(self.template.modulation) - local on=tostring(self.template.communication):lower() - if on=="true" then - self.radio.On=true - else - self.radio.On=false - end + self.radio.On=self.template.communication - -- Set default radio. - self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) - - -- Set callsign. + -- Set callsign. Default is set on spawn if not modified by user. local callsign=self.template.units[1].callsign if type(callsign)=="number" then -- Sometimes callsign is just "101". local cs=tostring(callsign) @@ -2627,6 +2620,10 @@ function FLIGHTGROUP:_InitGroup() else self.optionDefault.Formation=ENUMS.Formation.FixedWing.EchelonLeft.Group end + + -- Default TACAN off. + self:SetDefaultTACAN(nil, nil, nil, nil, true) + self.tacan=UTILS.DeepCopy(self.tacanDefault) -- Is this purely AI? self.ai=not self:_IsHuman(group) diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index a8d1b91e7..ed7cb5029 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -535,23 +535,19 @@ function NAVYGROUP:onafterSpawned(From, Event, To) self:SwitchROE(self.option.ROE) -- Set default Alarm State. - self:SwitchAlarmstate(self.option.ROT) + self:SwitchAlarmstate(self.option.Alarm) - -- Turn TACAN beacon on. - if self.tacan.On then - self:_SwitchTACAN(self.tacan) - end + -- Set TACAN beacon. + self:_SwitchTACAN() -- Turn ICLS on. - if self.icls.On then - self:_SwitchICLS(self.icls) - end + self:_SwitchICLS() - -- Turn on the radio. - if self.radioLast then - self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) + -- Set radio. + if self.radioDefault then + self:SwitchRadio() else - self.radio.On=true -- Radio is always on for ships. If not set, it is default. + self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, false) end end @@ -1077,14 +1073,11 @@ function NAVYGROUP:_InitGroup() -- Group ammo. self.ammo=self:GetAmmoTot() - -- Radio parameters from template. - self.radio.On=false -- Radio is always on for ships but we set it to false to check if it has been changed before spawn. + -- Radio parameters from template. Default is set on spawn if not modified by the user. + self.radio.On=true -- Radio is always on for ships. self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 self.radio.Modu=tonumber(self.template.units[1].modulation) - -- Set default radio. - self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, self.radio.On) - -- Set default formation. No really applicable for ships. self.optionDefault.Formation="Off Road" self.option.Formation=self.optionDefault.Formation diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index ade31b594..663c9cb61 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2412,32 +2412,34 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) if Mission.optionFormation then self:SwitchFormation() end - -- Radio freq and modu to last used. - if Mission.radio and self.radioLast then - self:SwitchRadio(self.radioLast.Freq, self.radioLast.Modu) + -- Radio freq and modu to default. + if Mission.radio then + self:SwitchRadio() end - -- TACAN + -- TACAN beacon. if Mission.tacan then - - if self.tacanDefault then - self:_SwitchTACAN(self.tacanDefault) - else - self:TurnOffTACAN() - end + + -- Switch to default. + self:_SwitchTACAN() + -- Return Squadron TACAN channel. local squadron=self.squadron --Ops.Squadron#SQUADRON if squadron then squadron:ReturnTacan(Mission.tacan.Channel) end + -- Set asset TACAN to nil. local asset=Mission:GetAssetByName(self.groupname) if asset then asset.tacan=nil end end - -- TODO: reset ICLS settings. + -- ICLS beacon to default. + if Mission.icls then + self:_SwitchICLS() + end -- Check if group is done. self:_CheckGroupDone(1) @@ -3538,7 +3540,7 @@ end -- @param #OPSGROUP self -- @return #number Current ROE. function OPSGROUP:GetROE() - return self.option.ROE + return self.option.ROE or self.optionDefault.ROE end --- Set the default ROT for the group. This is the ROT state gets when the group is spawned or to which it defaults back after a mission. @@ -3581,7 +3583,7 @@ end -- @param #OPSGROUP self -- @return #number Current ROT. function OPSGROUP:GetROT() - return self.option.ROT + return self.option.ROT or self.optionDefault.ROT end @@ -3639,23 +3641,37 @@ end -- @param #OPSGROUP self -- @return #number Current Alarm State. function OPSGROUP:GetAlarmstate() - return self.option.Alarm + return self.option.Alarm or self.optionDefault.Alarm end --- Set default TACAN parameters. -- @param #OPSGROUP self --- @param #number Channel TACAN channel. +-- @param #number Channel TACAN channel. Default is 74. -- @param #string Morse Morse code. Default "XXX". -- @param #string UnitName Name of the unit acting as beacon. -- @param #string Band TACAN mode. Default is "X" for ground and "Y" for airborne units. +-- @param #boolean OffSwitch If true, TACAN is off by default. -- @return #OPSGROUP self -function OPSGROUP:SetDefaultTACAN(Channel, Morse, UnitName, Band) +function OPSGROUP:SetDefaultTACAN(Channel, Morse, UnitName, Band, OffSwitch) self.tacanDefault={} - self.tacanDefault.Channel=Channel + self.tacanDefault.Channel=Channel or 74 self.tacanDefault.Morse=Morse or "XXX" self.tacanDefault.BeaconName=UnitName - self.tacanDefault.Band=Band + + if self.isAircraft then + Band=Band or "Y" + else + Band=Band or "X" + end + self.tacanDefault.Band=Band + + + if OffSwitch then + self.tacanDefault.On=false + else + self.tacanDefault.On=true + end return self end @@ -3663,10 +3679,24 @@ end --- Activate/switch TACAN beacon settings. -- @param #OPSGROUP self --- @param #OPSGROUP.Beacon Tacan Tacan data table. +-- @param #OPSGROUP.Beacon Tacan TACAN data table. Default is the default TACAN settings. -- @return #OPSGROUP self function OPSGROUP:_SwitchTACAN(Tacan) - self:SwitchTACAN(Tacan.Channel, Tacan.Morse, Tacan.BeaconName, Tacan.Band) + + if Tacan then + + self:SwitchTACAN(Tacan.Channel, Tacan.Morse, Tacan.BeaconName, Tacan.Band) + + else + + if self.tacanDefault.On then + self:SwitchTACAN() + else + self:TurnOffTACAN() + end + + end + end --- Activate/switch TACAN beacon settings. @@ -3678,10 +3708,19 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) - if self:IsAlive() or self:IsInUtero() then + if self:IsInUtero() then + + self:I(self.lid..string.format("Switching TACAN to DEFAULT when group is spawned")) + self:SetDefaultTACAN(Channel, Morse, UnitName, Band) + + elseif self:IsAlive() then + + Channel=Channel or self.tacanDefault.Channel + Morse=Morse or self.tacanDefault.Morse + Band=Band or self.tacanDefault.Band + UnitName=UnitName or self.tacanDefault.BeaconName + local unit=self:GetUnit(1) --Wrapper.Unit#UNIT - local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT - if UnitName then if type(UnitName)=="number" then unit=self.group:GetUnit(UnitName) @@ -3689,37 +3728,31 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) unit=UNIT:FindByName(UnitName) end end - + if not unit then - self:E(self.lid.."ERROR: Could not get TACAN unit. Trying first unit in the group.") - unit=self.group:GetUnit(1) + self:T(self.lid.."WARNING: Could not get TACAN unit. Trying first unit in the group") + unit=self:GetUnit(1) end - Channel=Channel or 74 - Morse=Morse or "XXX" - - if unit and unit:IsAlive() or self:IsInUtero() then + if unit and unit:IsAlive() then + -- Unit ID. local UnitID=unit:GetID() - local Type=BEACON.Type.TACAN - local System=BEACON.System.TACAN - + -- Type + local Type=BEACON.Type.TACAN + + -- System + local System=BEACON.System.TACAN if self.isAircraft then System=BEACON.System.TACAN_TANKER_Y - Band=Band or "Y" - else - System=BEACON.System.TACAN - Band=Band or "X" end -- Tacan frequency. local Frequency=UTILS.TACANToFrequency(Channel, Band) - - -- Backup TACAN. - if self.tacan.Channel then - self.tacanDefault=UTILS.DeepCopy(self.tacan) - end + + -- Activate beacon. + unit:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Band, true, Morse, true) -- Update info. self.tacan.Channel=Channel @@ -3728,24 +3761,16 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) self.tacan.BeaconName=unit:GetName() self.tacan.BeaconUnit=unit self.tacan.On=true - - if self:IsInUtero() then - self:T(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s when GROUP is SPAWNED", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) - else - - -- Activate beacon. - unit:CommandActivateBeacon(Type, System, Frequency, UnitID, Channel, Band, true, Morse, true) - - self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) - end - + + -- Debug info. + self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) else self:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive") end else - self:E(self.lid.."ERROR: Cound not set TACAN! Group is not alive") + self:E(self.lid.."ERROR: Cound not set TACAN! Group is not alive and not in utero any more") end return self @@ -3760,7 +3785,7 @@ function OPSGROUP:TurnOffTACAN() self.tacan.BeaconUnit:CommandDeactivateBeacon() end - self:T(self.lid..string.format("Switching TACAN OFF")) + self:I(self.lid..string.format("Switching TACAN OFF")) self.tacan.On=false end @@ -3777,45 +3802,93 @@ function OPSGROUP:GetTACAN() end + +--- Set default ICLS parameters. +-- @param #OPSGROUP self +-- @param #number Channel ICLS channel. Default is 1. +-- @param #string Morse Morse code. Default "XXX". +-- @param #string UnitName Name of the unit acting as beacon. +-- @param #boolean OffSwitch If true, TACAN is off by default. +-- @return #OPSGROUP self +function OPSGROUP:SetDefaultICLS(Channel, Morse, UnitName, OffSwitch) + + self.iclsDefault={} + self.iclsDefault.Channel=Channel or 1 + self.iclsDefault.Morse=Morse or "XXX" + self.iclsDefault.BeaconName=UnitName + + if OffSwitch then + self.iclsDefault.On=false + else + self.iclsDefault.On=true + end + + return self +end + + --- Activate/switch ICLS beacon settings. -- @param #OPSGROUP self -- @param #OPSGROUP.Beacon Icls ICLS data table. -- @return #OPSGROUP self function OPSGROUP:_SwitchICLS(Icls) - self:SwitchICLS(Icls.Channel, Icls.Morse, Icls.BeaconName) + + if Icls then + + self:SwitchICLS(Icls.Channel, Icls.Morse, Icls.BeaconName) + + else + + if self.iclsDefault.On then + self:SwitchICLS() + else + self:TurnOffICLS() + end + + end + end --- Activate/switch ICLS beacon settings. -- @param #OPSGROUP self --- @param #number Channel ICLS Channel. Default is 1. --- @param #string Morse ICLS morse code. Default is "XXX". +-- @param #number Channel ICLS Channel. Default is what is set in `SetDefaultICLS()` so usually channel 1. +-- @param #string Morse ICLS morse code. Default is what is set in `SetDefaultICLS()` so usually "XXX". -- @param #string UnitName Name of the unit in the group which should activate the ICLS beacon. Can also be given as #number to specify the unit number. Default is the first unit of the group. -- @return #OPSGROUP self function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) - if self:IsAlive() or self:IsInUtero() then + if self:IsInUtero() then + + self:SetDefaultICLS(Channel,Morse,UnitName) + + self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.iclsDefault.Channel, tostring(self.iclsDefault.Morse), self.iclsDefault.BeaconName)) - local unit=self.group:GetUnit(1) --Wrapper.Unit#UNIT + elseif self:IsAlive() then + + Channel=Channel or self.iclsDefault.Channel + Morse=Morse or self.iclsDefault.Morse + local unit=self:GetUnit(1) --Wrapper.Unit#UNIT if UnitName then if type(UnitName)=="number" then - unit=self.group:GetUnit(UnitName) + unit=self:GetUnit(UnitName) else unit=UNIT:FindByName(UnitName) end end if not unit then - self:E(self.lid.."ERROR: Could not get ICLS unit. Trying first unit in the group.") - unit=self.group:GetUnit(1) + self:T(self.lid.."WARNING: Could not get ICLS unit. Trying first unit in the group") + unit=self:GetUnit(1) end - - Channel=Channel or 1 - Morse=Morse or "XXX" - if unit and unit:IsAlive() or self:IsInUtero() then + if unit and unit:IsAlive() then - local UnitID=unit:GetID() + -- Unit ID. + local UnitID=unit:GetID() + + -- Activate beacon. + unit:CommandActivateICLS(Channel, UnitID, Morse) -- Update info. self.icls.Channel=Channel @@ -3825,16 +3898,8 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) self.icls.BeaconUnit=unit self.icls.On=true - if self:IsInUtero() then - self:T2(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s when GROUP is SPAWNED", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) - else - - -- Activate beacon. - unit:CommandActivateICLS(Channel, UnitID, Morse) - - self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) - - end + -- Debug info. + self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) else self:E(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") @@ -3896,58 +3961,36 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchRadio(Frequency, Modulation) - Frequency=Frequency or self.radioDefault.Freq - Modulation=Modulation or self.radioDefault.Modu - if self:IsInUtero() then - -- Set current radio. - self.radioLast={} - self.radioLast.Freq=Frequency - self.radioLast.Modu=Modulation - - self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radioLast.Freq, UTILS.GetModulationName(self.radioLast.Modu))) + -- Set default radio. + self:SetDefaultRadio(Frequency, Modulation) - elseif self:IsAlive() then - - local group=self.group --Wrapper.Group#GROUP + -- Debug info. + self:T2(self.lid..string.format("Switching radio to frequency %.3f MHz %s when GROUP is SPAWNED", self.radioDefault.Freq, UTILS.GetModulationName(self.radioDefault.Modu))) + + elseif self:IsAlive() then + + Frequency=Frequency or self.radioDefault.Freq + Modulation=Modulation or self.radioDefault.Modu if self.isAircraft and not self.radio.On then - group:SetOption(AI.Option.Air.id.SILENCE, false) - end + self.group:SetOption(AI.Option.Air.id.SILENCE, false) + end + + -- Give command + self.group:CommandSetFrequency(Frequency, Modulation) - -- Backup last radio settings. - if self.radio then - self.radioLast=UTILS.DeepCopy(self.radio) - end - - -- Debug. - if false then - local text=string.format("\nRadio Freq=%.3f %.3f", self.radio.Freq, self.radioLast.Freq) - text=text..string.format("\nRadio Modu=%d %d", self.radio.Modu, self.radioLast.Modu) - text=text..string.format("\nRadio OnOf=%s %s", tostring(self.radio.On), tostring(self.radioLast.On)) - self:I(self.lid..text) - end - - -- Set current radio. + -- Update current settings. self.radio.Freq=Frequency self.radio.Modu=Modulation self.radio.On=true - -- Only switch radio if different. - if self.radio.Freq~=self.radioLast.Freq or self.radio.Modu~=self.radioLast.Modu then - - -- Give command - group:CommandSetFrequency(Frequency, Modulation) - - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) - - else - self:T(self.lid.."INFO: Current radio not switched as freq/modulation did not change") - end - + -- Debug info. + self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + else - self:E(self.lid.."ERROR: Cound not set Radio! Group is not alive") + self:E(self.lid.."ERROR: Cound not set Radio! Group is not alive or not in utero any more") end return self @@ -4007,7 +4050,7 @@ function OPSGROUP:SwitchFormation(Formation) elseif self.isGround then - -- TODO: here we need to update the route. + -- Polymorphic and overwritten in ARMYGROUP. else self:E(self.lid.."ERROR: Formation can only be set for aircraft or ground units!") @@ -4027,7 +4070,7 @@ end ---- Set default formation. +--- Set default callsign. -- @param #OPSGROUP self -- @param #number CallsignName Callsign name. -- @param #number CallsignNumber Callsign number. @@ -4048,9 +4091,6 @@ end -- @return #OPSGROUP self function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) - CallsignName=CallsignName or self.callsignDefault.NumberSquad - CallsignNumber=CallsignNumber or self.callsignDefault.NumberGroup - if self:IsInUtero() then -- Set default callsign. We switch to this when group is spawned. @@ -4058,6 +4098,9 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) elseif self:IsAlive() then + CallsignName=CallsignName or self.callsignDefault.NumberSquad + CallsignNumber=CallsignNumber or self.callsignDefault.NumberGroup + -- Set current callsign. self.callsign.NumberSquad=CallsignName self.callsign.NumberGroup=CallsignNumber From 19b870c0384e3c24c2be88d37b1d3037a785c163 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 19 Sep 2020 23:52:17 +0200 Subject: [PATCH 78/79] Ops - Less traceing. --- Moose Development/Moose/Ops/OpsGroup.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 663c9cb61..dc9030d4d 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -2867,7 +2867,7 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) - self:I(self.lid..string.format("Group dead!")) + self:T(self.lid..string.format("Group dead!")) -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil @@ -2908,7 +2908,7 @@ function OPSGROUP:onafterStop(From, Event, To) end -- Debug output. - self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + self:T(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3710,7 +3710,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) if self:IsInUtero() then - self:I(self.lid..string.format("Switching TACAN to DEFAULT when group is spawned")) + self:T(self.lid..string.format("Switching TACAN to DEFAULT when group is spawned")) self:SetDefaultTACAN(Channel, Morse, UnitName, Band) elseif self:IsAlive() then @@ -3763,7 +3763,7 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) self.tacan.On=true -- Debug info. - self:I(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) + self:T(self.lid..string.format("Switching TACAN to Channel %d%s Morse %s on unit %s", self.tacan.Channel, self.tacan.Band, tostring(self.tacan.Morse), self.tacan.BeaconName)) else self:E(self.lid.."ERROR: Cound not set TACAN! Unit is not alive") @@ -3785,7 +3785,7 @@ function OPSGROUP:TurnOffTACAN() self.tacan.BeaconUnit:CommandDeactivateBeacon() end - self:I(self.lid..string.format("Switching TACAN OFF")) + self:T(self.lid..string.format("Switching TACAN OFF")) self.tacan.On=false end @@ -3899,7 +3899,7 @@ function OPSGROUP:SwitchICLS(Channel, Morse, UnitName) self.icls.On=true -- Debug info. - self:I(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) + self:T(self.lid..string.format("Switching ICLS to Channel %d Morse %s on unit %s", self.icls.Channel, tostring(self.icls.Morse), self.icls.BeaconName)) else self:E(self.lid.."ERROR: Cound not set ICLS! Unit is not alive.") @@ -3987,7 +3987,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) self.radio.On=true -- Debug info. - self:I(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) + self:T(self.lid..string.format("Switching radio to frequency %.3f MHz %s", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu))) else self:E(self.lid.."ERROR: Cound not set Radio! Group is not alive or not in utero any more") @@ -4061,7 +4061,7 @@ function OPSGROUP:SwitchFormation(Formation) self.option.Formation=Formation -- Debug info. - self:I(self.lid..string.format("Switching formation to %d", self.option.Formation)) + self:T(self.lid..string.format("Switching formation to %d", self.option.Formation)) end @@ -4106,7 +4106,7 @@ function OPSGROUP:SwitchCallsign(CallsignName, CallsignNumber) self.callsign.NumberGroup=CallsignNumber -- Debug. - self:I(self.lid..string.format("Switching callsign to %d-%d", self.callsign.NumberSquad, self.callsign.NumberGroup)) + self:T(self.lid..string.format("Switching callsign to %d-%d", self.callsign.NumberSquad, self.callsign.NumberGroup)) -- Give command to change the callsign. self.group:CommandSetCallsign(self.callsign.NumberSquad, self.callsign.NumberGroup) From 23f6752362fe0f5d1bfba7e4e50d3549b4117fff Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 19 Sep 2020 23:55:45 +0200 Subject: [PATCH 79/79] Ops - rm staff --- Moose Development/Moose/Modules.lua | 2 - Moose Development/Moose/Ops/ChiefOfStaff.lua | 725 ------------------ Moose Development/Moose/Ops/WingCommander.lua | 387 ---------- Moose Setup/Moose.files | 2 - 4 files changed, 1116 deletions(-) delete mode 100644 Moose Development/Moose/Ops/ChiefOfStaff.lua delete mode 100644 Moose Development/Moose/Ops/WingCommander.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index f1b6d1145..8e1cd2860 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -82,8 +82,6 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/WingCommander.lua' ) -__Moose.Include( 'Scripts/Moose/Ops/ChiefOfStaff.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/ChiefOfStaff.lua b/Moose Development/Moose/Ops/ChiefOfStaff.lua deleted file mode 100644 index 6655ff463..000000000 --- a/Moose Development/Moose/Ops/ChiefOfStaff.lua +++ /dev/null @@ -1,725 +0,0 @@ ---- **Ops** - Chief of Staff. --- --- **Main Features:** --- --- * Stuff --- --- === --- --- ### Author: **funkyfranky** --- @module Ops.Chief --- @image OPS_Chief.png - - ---- CHIEF class. --- @type CHIEF --- @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 #table missionqueue Mission queue. --- @field Core.Set#SET_ZONE borderzoneset Set of zones defining the border of our territory. --- @field Core.Set#SET_ZONE yellowzoneset Set of zones defining the extended border. Defcon is set to YELLOW if enemy activity is detected. --- @field Core.Set#SET_ZONE engagezoneset Set of zones where enemies are actively engaged. --- @field #string Defcon Defence condition. --- @field Ops.WingCommander#WINGCOMMANDER wingcommander Wing commander, commanding airborne forces. --- @field Ops.Admiral#ADMIRAL admiral Admiral commanding navy forces. --- @field Ops.General#GENERAL genaral General commanding army forces. --- @extends Ops.Intelligence#INTEL - ---- Be surprised! --- --- === --- --- ![Banner Image](..\Presentations\WingCommander\CHIEF_Main.jpg) --- --- # The CHIEF Concept --- --- The Chief of staff gathers intel and assigns missions (AUFTRAG) the airforce (WINGCOMMANDER), army (GENERAL) or navy (ADMIRAL). --- --- **Note** that currently only assignments to airborne forces (WINGCOMMANDER) are implemented. --- --- --- @field #CHIEF -CHIEF = { - ClassName = "CHIEF", - Debug = nil, - lid = nil, - wingcommander = nil, - admiral = nil, - general = nil, - missionqueue = {}, - borderzoneset = nil, - yellowzoneset = nil, - engagezoneset = nil, -} - ---- Defence condition. --- @type CHIEF.DEFCON --- @field #string GREEN No enemy activities detected. --- @field #string YELLOW Enemy near our border. --- @field #string RED Enemy within our border. -CHIEF.DEFCON = { - GREEN="Green", - YELLOW="Yellow", - RED="Red", -} - ---- CHIEF class version. --- @field #string version -CHIEF.version="0.0.1" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO list -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- TODO: Define A2A and A2G parameters. --- DONE: Add/remove spawned flightgroups to detection set. --- DONE: Borderzones. --- NOGO: Maybe it's possible to preselect the assets for the mission. - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructor -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new CHIEF object and start the FSM. --- @param #CHIEF self --- @param Core.Set#SET_GROUP AgentSet Set of agents (groups) providing intel. Default is an empty set. --- @param #number Coalition Coalition side, e.g. `coaliton.side.BLUE`. Can also be passed as a string "red", "blue" or "neutral". --- @return #CHIEF self -function CHIEF:New(AgentSet, Coalition) - - AgentSet=AgentSet or SET_GROUP:New() - - -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, INTEL:New(AgentSet, Coalition)) --#CHIEF - - -- Set some string id for output to DCS.log file. - --self.lid=string.format("CHIEF | ") - - self:SetBorderZones() - self:SetYellowZones() - - self:SetThreatLevelRange() - - self.Defcon=CHIEF.DEFCON.GREEN - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("*", "AssignMissionAirforce", "*") -- Assign mission to a WINGCOMMANDER. - self:AddTransition("*", "AssignMissionNavy", "*") -- Assign mission to an ADMIRAL. - self:AddTransition("*", "AssignMissionArmy", "*") -- Assign mission to a GENERAL. - self:AddTransition("*", "CancelMission", "*") -- Cancel mission. - self:AddTransition("*", "Defcon", "*") -- Change defence condition. - self:AddTransition("*", "DeclareWar", "*") -- Declare War. - - ------------------------ - --- Pseudo Functions --- - ------------------------ - - --- Triggers the FSM event "Start". Starts the CHIEF. Initializes parameters and starts event handlers. - -- @function [parent=#CHIEF] Start - -- @param #CHIEF self - - --- Triggers the FSM event "Start" after a delay. Starts the CHIEF. Initializes parameters and starts event handlers. - -- @function [parent=#CHIEF] __Start - -- @param #CHIEF self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Stop". Stops the CHIEF and all its event handlers. - -- @param #CHIEF self - - --- Triggers the FSM event "Stop" after a delay. Stops the CHIEF and all its event handlers. - -- @function [parent=#CHIEF] __Stop - -- @param #CHIEF self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Status". - -- @function [parent=#CHIEF] Status - -- @param #CHIEF self - - --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#CHIEF] __Status - -- @param #CHIEF 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 - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- User functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set this to be an air-to-any dispatcher, i.e. engaging air, ground and naval targets. This is the default anyway. --- @param #CHIEF self --- @return #CHIEF self -function CHIEF:SetAirToAny() - - self:SetFilterCategory({}) - - return self -end - ---- Set this to be an air-to-air dispatcher. --- @param #CHIEF self --- @return #CHIEF self -function CHIEF:SetAirToAir() - - self:SetFilterCategory({Unit.Category.AIRPLANE, Unit.Category.HELICOPTER}) - - return self -end - ---- Set this to be an air-to-ground dispatcher, i.e. engage only ground units --- @param #CHIEF self --- @return #CHIEF self -function CHIEF:SetAirToGround() - - self:SetFilterCategory({Unit.Category.GROUND_UNIT}) - - return self -end - ---- Set this to be an air-to-sea dispatcher, i.e. engage only naval units. --- @param #CHIEF self --- @return #CHIEF self -function CHIEF:SetAirToSea() - - self:SetFilterCategory({Unit.Category.SHIP}) - - return self -end - ---- Set this to be an air-to-surface dispatcher, i.e. engaging ground and naval groups. --- @param #CHIEF self --- @return #CHIEF self -function CHIEF:SetAirToSurface() - - self:SetFilterCategory({Unit.Category.GROUND_UNIT, Unit.Category.SHIP}) - - return self -end - ---- Set a threat level range that will be engaged. Threat level is a number between 0 and 10, where 10 is a very dangerous threat. --- Targets with threat level 0 are usually harmless. --- @param #CHIEF self --- @param #number ThreatLevelMin Min threat level. Default 1. --- @param #number ThreatLevelMax Max threat level. Default 10. --- @return #CHIEF self -function CHIEF:SetThreatLevelRange(ThreatLevelMin, ThreatLevelMax) - - self.threatLevelMin=ThreatLevelMin or 1 - self.threatLevelMax=ThreatLevelMax or 10 - - return self -end - ---- Set defence condition. --- @param #CHIEF self --- @param #string Defcon Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`. --- @return #CHIEF self -function CHIEF:SetDefcon(Defcon) - - self.Defcon=Defcon - --self:Defcon(Defcon) - - return self -end - - ---- Set the wing commander for the airforce. --- @param #CHIEF self --- @param Ops.WingCommander#WINGCOMMANDER WingCommander The WINGCOMMANDER object. --- @return #CHIEF self -function CHIEF:SetWingCommander(WingCommander) - - self.wingcommander=WingCommander - - self.wingcommander.chief=self - - return self -end - ---- Add mission to mission queue. --- @param #CHIEF self --- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. --- @return #CHIEF self -function CHIEF:AddMission(Mission) - - table.insert(self.missionqueue, Mission) - - return self -end - ---- Remove mission from queue. --- @param #CHIEF self --- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. --- @return #CHIEF self -function CHIEF:RemoveMission(Mission) - - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission.auftragsnummer==Mission.auftragsnummer then - self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) - table.remove(self.missionqueue, i) - break - end - - end - - return self -end - ---- Set border zone set. --- @param #CHIEF self --- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. --- @return #CHIEF self -function CHIEF:SetBorderZones(BorderZoneSet) - - -- Border zones. - self.borderzoneset=BorderZoneSet or SET_ZONE:New() - - return self -end - ---- Add a zone defining your territory. --- @param #CHIEF self --- @param Core.Zone#ZONE BorderZone The zone defining the border of your territory. --- @return #CHIEF self -function CHIEF:AddBorderZone(BorderZone) - - -- Add a border zone. - self.borderzoneset:AddZone(BorderZone) - - return self -end - ---- Set yellow zone set. Detected enemy troops in this zone will trigger defence condition YELLOW. --- @param #CHIEF self --- @param Core.Set#SET_ZONE YellowZoneSet Set of zones, defining our borders. --- @return #CHIEF self -function CHIEF:SetYellowZones(YellowZoneSet) - - -- Border zones. - self.yellowzoneset=YellowZoneSet or SET_ZONE:New() - - return self -end - ---- Add a zone defining an area outside your territory that is monitored for enemy activity. --- @param #CHIEF self --- @param Core.Zone#ZONE YellowZone The zone defining the border of your territory. --- @return #CHIEF self -function CHIEF:AddYellowZone(YellowZone) - - -- Add a border zone. - self.yellowzoneset:AddZone(YellowZone) - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start & Status -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- On after Start event. --- @param #CHIEF self --- @param Wrapper.Group#GROUP Group Flight group. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function CHIEF:onafterStart(From, Event, To) - - -- Short info. - local text=string.format("Starting Chief of Staff") - self:I(self.lid..text) - - -- Start parent INTEL. - self:GetParent(self).onafterStart(self, From, Event, To) - - -- Start wingcommander. - if self.wingcommander then - if self.wingcommander:GetState()=="NotReadyYet" then - self.wingcommander:Start() - end - end - -end - ---- On after "Status" event. --- @param #CHIEF self --- @param Wrapper.Group#GROUP Group Flight group. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function CHIEF:onafterStatus(From, Event, To) - - -- Start parent INTEL. - self:GetParent(self).onafterStatus(self, From, Event, To) - - -- FSM state. - local fsmstate=self:GetState() - - - -- Clean up missions where the contact was lost. - for _,_contact in pairs(self.ContactsLost) do - local contact=_contact --#INTEL.Contact - - if contact.mission and contact.mission:IsNotOver() then - - local text=string.format("Lost contact to target %s! %s mission %s will be cancelled.", contact.groupname, contact.mission.type:upper(), contact.mission.name) - MESSAGE:New(text, 120, "CHIEF"):ToAll() - self:I(self.lid..text) - - -- Cancel this mission. - contact.mission:Cancel() - - end - - end - - -- Create missions for all new contacts. - local Nred=0 ; local Nyellow=0 ; local Nengage=0 - for _,_contact in pairs(self.Contacts) do - local contact=_contact --#CHIEF.Contact - local group=contact.group --Wrapper.Group#GROUP - - local inred=self:CheckGroupInBorder(group) - if inred then - Nred=Nred+1 - end - - local inyellow=self:CheckGroupInYellow(group) - if inyellow then - Nyellow=Nyellow+1 - end - - -- Is this a threat? - local threat=contact.threatlevel>=self.threatLevelMin and contact.threatlevel<=self.threatLevelMax - - local redalert=true - if self.borderzoneset:Count()>0 then - redalert=inred - end - - if redalert and threat and not contact.mission then - - -- Create a mission based on group category. - local mission=AUFTRAG:NewAUTO(group) - - -- Add mission to queue. - if mission then - - --TODO: Better amount of necessary assets. Count units in asset and in contact. Might need nassetMin/Max. - mission.nassets=1 - - -- Missons are repeated max 3 times on failure. - mission.NrepeatFailure=3 - - -- Set mission contact. - contact.mission=mission - - -- Add mission to queue. - self:AddMission(mission) - end - - end - - end - - --- - -- Defcon - --- - - -- TODO: Need to introduce time check to avoid fast oscillation between different defcon states in case groups move in and out of the zones. - if Nred>0 then - self:SetDefcon(CHIEF.DEFCON.RED) - elseif Nyellow>0 then - self:SetDefcon(CHIEF.DEFCON.YELLOW) - else - self:SetDefcon(CHIEF.DEFCON.GREEN) - end - - --- - -- Mission Queue - --- - - -- Check mission queue and assign one PLANNED mission. - self:CheckMissionQueue() - - local text=string.format("Defcon=%s Missions=%d Contacts: Total=%d Yellow=%d Red=%d", self.Defcon, #self.missionqueue, #self.Contacts, Nyellow, Nred) - self:I(self.lid..text) - - --- - -- Contacts - --- - - -- Info about contacts. - if #self.Contacts>0 then - local text="Contacts:" - for i,_contact in pairs(self.Contacts) do - local contact=_contact --#CHIEF.Contact - local mtext="N/A" - if contact.mission then - mtext=string.format("Mission %s (%s) %s", contact.mission.name, contact.mission.type, contact.mission.status:upper()) - end - text=text..string.format("\n[%d] %s Type=%s (%s): Threat=%d Mission=%s", i, contact.groupname, contact.categoryname, contact.typename, contact.threatlevel, mtext) - end - self:I(self.lid..text) - end - - -- Mission queue. - if #self.missionqueue>0 then - local text="Mission queue:" - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - local target=mission:GetTargetName() or "unknown" - - text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) - end - self:I(self.lid..text) - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FSM Events -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- On after "AssignMissionAssignAirforce" event. --- @param #CHIEF self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission. -function CHIEF:onafterAssignMissionAirforce(From, Event, To, Mission) - - if self.wingcommander then - self:I(self.lid..string.format("Assigning mission %s (%s) to WINGCOMMANDER", Mission.name, Mission.type)) - self.wingcommander:AddMission(Mission) - else - self:E(self.lid..string.format("Mission cannot be assigned as no WINGCOMMANDER is defined.")) - end - -end - ---- On after "CancelMission" event. --- @param #CHIEF self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission. -function CHIEF:onafterCancelMission(From, Event, To, Mission) - - self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) - - if Mission.status==AUFTRAG.Status.PLANNED then - - -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. - self:RemoveMission(Mission) - - else - - -- Airwing will cancel mission. - if Mission.airwing then - Mission.airwing:MissionCancel(Mission) - end - - end - -end - ---- On before "Defcon" event. --- @param #CHIEF self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Defcon New defence condition. -function CHIEF:onbeforeDefcon(From, Event, To, Defcon) - - local gotit=false - for _,defcon in pairs(CHIEF.DEFCON) do - if defcon==Defcon then - gotit=true - end - end - - if not gotit then - self:E(self.lid..string.format("ERROR: Unknown DEFCON specified! Dont know defcon=%s", tostring(Defcon))) - return false - end - - -- Defcon did not change. - if Defcon==self.Defcon then - self:I(self.lid..string.format("Defcon %s unchanged. No processing transition.", tostring(Defcon))) - return false - end - - return true -end - ---- On after "Defcon" event. --- @param #CHIEF self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #string Defcon New defence condition. -function CHIEF:onafterDefcon(From, Event, To, Defcon) - self:I(self.lid..string.format("Changing Defcon from %s --> %s", self.Defcon, Defcon)) - - -- Set new defcon. - self.Defcon=Defcon -end - ---- On after "DeclareWar" event. --- @param #CHIEF self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param #CHIEF Chief The Chief we declared war on. -function CHIEF:onafterDeclareWar(From, Event, To, Chief) - - if Chief then - self:AddWarOnChief(Chief) - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Resources -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Check mission queue and assign ONE planned mission. --- @param #CHIEF self -function CHIEF:CheckMissionQueue() - - -- TODO: Sort mission queue. wrt what? Threat level? - - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- We look for PLANNED missions. - if mission.status==AUFTRAG.Status.PLANNED then - - --- - -- PLANNNED Mission - --- - - -- Check if there is an airwing that can do the mission. - local airwing=self:GetAirwingForMission(mission) - - if airwing then - - -- Add mission to airwing. - self:AssignMissionAirforce(mission) - - return - - else - self:T(self.lid.."NO airwing") - end - - else - - --- - -- Missions NOT in PLANNED state - --- - - end - - end - -end - ---- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. --- @param #CHIEF self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return Ops.AirWing#AIRWING The airwing best for this mission. -function CHIEF:GetAirwingForMission(Mission) - - if self.wingcommander then - return self.wingcommander:GetAirwingForMission(Mission) - end - - return nil -end - ---- Check if group is inside our border. --- @param #CHIEF self --- @param Wrapper.Group#GROUP group The group. --- @return #boolean If true, group is in any zone. -function CHIEF:CheckGroupInBorder(group) - - local inside=self:CheckGroupInZones(group, self.borderzoneset) - - return inside -end - ---- Check if group is near our border (yellow zone). --- @param #CHIEF self --- @param Wrapper.Group#GROUP group The group. --- @return #boolean If true, group is in any zone. -function CHIEF:CheckGroupInYellow(group) - - -- Check inside yellow but not inside our border. - local inside=self:CheckGroupInZones(group, self.yellowzoneset) and not self:CheckGroupInZones(group, self.borderzoneset) - - return inside -end - ---- Check if group is inside a zone. --- @param #CHIEF self --- @param Wrapper.Group#GROUP group The group. --- @param Core.Set#SET_ZONE zoneset Set of zones. --- @return #boolean If true, group is in any zone. -function CHIEF:CheckGroupInZones(group, zoneset) - - for _,_zone in pairs(zoneset.Set or {}) do - local zone=_zone --Core.Zone#ZONE - - if group:IsPartlyOrCompletelyInZone(zone) then - return true - end - end - - return false -end - ---- Check resources. --- @param #CHIEF self --- @return #table -function CHIEF:CheckResources() - - local capabilities={} - - for _,MissionType in pairs(AUFTRAG.Type) do - capabilities[MissionType]=0 - - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - - -- Get Number of assets that can do this type of missions. - local _,assets=airwing:CanMission(MissionType) - - -- Add up airwing resources. - capabilities[MissionType]=capabilities[MissionType]+#assets - end - - end - - return capabilities -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file diff --git a/Moose Development/Moose/Ops/WingCommander.lua b/Moose Development/Moose/Ops/WingCommander.lua deleted file mode 100644 index 66ecfd6dd..000000000 --- a/Moose Development/Moose/Ops/WingCommander.lua +++ /dev/null @@ -1,387 +0,0 @@ ---- **Ops** - Commander Air Wing. --- --- **Main Features:** --- --- * Manages AIRWINGS --- * Handles missions (AUFTRAG) and finds the best airwing able to do the job --- --- === --- --- ### Author: **funkyfranky** --- @module Ops.WingCommander --- @image OPS_WingCommander.png - - ---- WINGCOMMANDER class. --- @type WINGCOMMANDER --- @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 #table airwings Table of airwings which are commanded. --- @field #table missionqueue Mission queue. --- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. --- @extends Core.Fsm#FSM - ---- Be surprised! --- --- === --- --- ![Banner Image](..\Presentations\WingCommander\WINGCOMMANDER_Main.jpg) --- --- # The WINGCOMMANDER Concept --- --- A wing commander is the head of airwings. He will find the best AIRWING to perform an assigned AUFTRAG (mission). --- --- --- @field #WINGCOMMANDER -WINGCOMMANDER = { - ClassName = "WINGCOMMANDER", - Debug = nil, - lid = nil, - airwings = {}, - missionqueue = {}, -} - ---- WINGCOMMANDER class version. --- @field #string version -WINGCOMMANDER.version="0.1.0" - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO list -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- TODO: Improve airwing selection. Mostly done! --- NOGO: Maybe it's possible to preselect the assets for the mission. - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Constructor -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Create a new WINGCOMMANDER object and start the FSM. --- @param #WINGCOMMANDER self --- @return #WINGCOMMANDER self -function WINGCOMMANDER:New() - - -- Inherit everything from INTEL class. - local self=BASE:Inherit(self, FSM:New()) --#WINGCOMMANDER - - self.lid="WINGCOMMANDER | " - - -- Start state. - self:SetStartState("NotReadyYet") - - -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("NotReadyYet", "Start", "OnDuty") -- Start WC. - self:AddTransition("*", "Status", "*") -- Status report. - self:AddTransition("*", "Stop", "Stopped") -- Stop WC. - - self:AddTransition("*", "AssignMission", "*") -- Mission was assigned to an AIRWING. - self:AddTransition("*", "CancelMission", "*") -- Cancel mission. - - ------------------------ - --- Pseudo Functions --- - ------------------------ - - --- Triggers the FSM event "Start". Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. - -- @function [parent=#WINGCOMMANDER] Start - -- @param #WINGCOMMANDER self - - --- Triggers the FSM event "Start" after a delay. Starts the WINGCOMMANDER. Initializes parameters and starts event handlers. - -- @function [parent=#WINGCOMMANDER] __Start - -- @param #WINGCOMMANDER self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Stop". Stops the WINGCOMMANDER and all its event handlers. - -- @param #WINGCOMMANDER self - - --- Triggers the FSM event "Stop" after a delay. Stops the WINGCOMMANDER and all its event handlers. - -- @function [parent=#WINGCOMMANDER] __Stop - -- @param #WINGCOMMANDER self - -- @param #number delay Delay in seconds. - - --- Triggers the FSM event "Status". - -- @function [parent=#WINGCOMMANDER] Status - -- @param #WINGCOMMANDER self - - --- Triggers the FSM event "Status" after a delay. - -- @function [parent=#WINGCOMMANDER] __Status - -- @param #WINGCOMMANDER 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 -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Add an airwing to the wingcommander. --- @param #WINGCOMMANDER self --- @param Ops.AirWing#AIRWING Airwing The airwing to add. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddAirwing(Airwing) - - -- This airwing is managed by this wing commander. - Airwing.wingcommander=self - - table.insert(self.airwings, Airwing) - - return self -end - ---- Add mission to mission queue. --- @param #WINGCOMMANDER self --- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:AddMission(Mission) - - Mission.wingcommander=self - - table.insert(self.missionqueue, Mission) - - return self -end - ---- Remove mission from queue. --- @param #WINGCOMMANDER self --- @param Ops.Auftrag#AUFTRAG Mission Mission to be removed. --- @return #WINGCOMMANDER self -function WINGCOMMANDER:RemoveMission(Mission) - - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - if mission.auftragsnummer==Mission.auftragsnummer then - self:I(self.lid..string.format("Removing mission %s (%s) status=%s from queue", Mission.name, Mission.type, Mission.status)) - table.remove(self.missionqueue, i) - break - end - - end - - return self -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start & Status -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- On after Start event. Starts the FLIGHTGROUP FSM and event handlers. --- @param #WINGCOMMANDER self --- @param Wrapper.Group#GROUP Group Flight group. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function WINGCOMMANDER:onafterStart(From, Event, To) - - -- Short info. - local text=string.format("Starting Wing Commander") - self:I(self.lid..text) - - -- Start attached airwings. - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - if airwing:GetState()=="NotReadyYet" then - airwing:Start() - end - end - - self:__Status(-1) -end - ---- On after "Status" event. --- @param #WINGCOMMANDER self --- @param Wrapper.Group#GROUP Group Flight group. --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function WINGCOMMANDER:onafterStatus(From, Event, To) - - -- FSM state. - local fsmstate=self:GetState() - - -- Check mission queue and assign one PLANNED mission. - self:CheckMissionQueue() - - -- Mission queue. - if #self.missionqueue>0 then - - local text="Mission queue:" - for i,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - local target=mission:GetTargetName() or "unknown" - - text=text..string.format("\n[%d] %s (%s): status=%s, target=%s", i, mission.name, mission.type, mission.status, target) - end - self:I(self.lid..text) - - end - - self:__Status(-30) -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- FSM Events -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- On after "AssignMission" event. Mission is added to the AIRWING mission queue. --- @param #WINGCOMMANDER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.AirWing#AIRWING Airwing The AIRWING. --- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterAssignMission(From, Event, To, Airwing, Mission) - - self:I(self.lid..string.format("Assigning mission %s (%s) to airwing %s", Mission.name, Mission.type, Airwing.alias)) - Airwing:AddMission(Mission) - -end - ---- On after "CancelMission" event. --- @param #WINGCOMMANDER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Ops.Auftrag#AUFTRAG Mission The mission. -function WINGCOMMANDER:onafterCancelMission(From, Event, To, Mission) - - self:I(self.lid..string.format("Cancelling mission %s (%s) in status %s", Mission.name, Mission.type, Mission.status)) - - if Mission.status==AUFTRAG.Status.PLANNED then - - -- Mission is still in planning stage. Should not have an airbase assigned ==> Just remove it form the queue. - self:RemoveMission(Mission) - - else - - -- Airwing will cancel mission. - if Mission.airwing then - Mission.airwing:CancelMission(Mission) - end - - end - -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Resources -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Check mission queue and assign ONE planned mission. --- @param #WINGCOMMANDER self -function WINGCOMMANDER:CheckMissionQueue() - - -- TODO: Sort mission queue. wrt what? Threat level? - - for _,_mission in pairs(self.missionqueue) do - local mission=_mission --Ops.Auftrag#AUFTRAG - - -- We look for PLANNED missions. - if mission.status==AUFTRAG.Status.PLANNED then - - --- - -- PLANNNED Mission - --- - - local airwing=self:GetAirwingForMission(mission) - - if airwing then - - -- Add mission to airwing. - self:AssignMission(airwing, mission) - - return - end - - else - - --- - -- Missions NOT in PLANNED state - --- - - end - - end - -end - ---- Check all airwings if they are able to do a specific mission type at a certain location with a given number of assets. --- @param #WINGCOMMANDER self --- @param Ops.Auftrag#AUFTRAG Mission The mission. --- @return Ops.AirWing#AIRWING The airwing best for this mission. -function WINGCOMMANDER:GetAirwingForMission(Mission) - - -- Table of airwings that can do the mission. - local airwings={} - - -- Loop over all airwings. - for _,_airwing in pairs(self.airwings) do - local airwing=_airwing --Ops.AirWing#AIRWING - - -- Check if airwing can do this mission. - local can,assets=airwing:CanMission(Mission) - - -- Can it? - if can then - - -- Get coordinate of the target. - local coord=Mission:GetTargetCoordinate() - - if coord then - - -- Distance from airwing to target. - local dist=UTILS.MetersToNM(coord:Get2DDistance(airwing:GetCoordinate())) - - -- Add airwing to table of airwings that can. - table.insert(airwings, {airwing=airwing, dist=dist, targetcoord=coord, nassets=#assets}) - - end - - end - - end - - -- Can anyone? - if #airwings>0 then - - --- Something like: - -- * Closest airwing that can should be first prio. - -- * However, there should be a certain "quantization". if wing is 50 or 60 NM way should not really matter. In that case, the airwing with more resources should get the job. - local function score(a) - local d=math.round(a.dist/10) - end - - -- Sort table wrt distance and number of assets. - -- Distances within 10 NM are equal and the airwing with more assets is preferred. - local function sortdist(a,b) - local ad=math.round(a.dist/10) -- dist 55 NM ==> 5.5 ==> 6 - local bd=math.round(b.dist/10) -- dist 63 NM ==> 6.3 ==> 6 - return adb.nassets) - end - table.sort(airwings, sortdist) - - -- This is the closest airwing to the target. - local airwing=airwings[1].airwing --Ops.AirWing#AIRWING - - return airwing - end - - return nil -end - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 85249125e..f92bb5664 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -75,8 +75,6 @@ Ops/NavyGroup.lua Ops/Squadron.lua Ops/AirWing.lua Ops/Intelligence.lua -Ops/WingCommander.lua -Ops/ChiefOfStaff.lua AI/AI_Balancer.lua AI/AI_Air.lua