From ae54cd8fde3044ac6ee1c991766a59ee66b542a9 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 27 May 2022 22:14:21 +0200 Subject: [PATCH] OPS ** ARMYGROUP** - Added suppression option **COMMANDER** - Added function to add targets with defined resources **OPSGROUP** - Added option to pause multiple missions **INTEL** - Fixed bug in cluster calc **LEGION** - Added function to get alive opsgroups **TARGET** * Added start condition --- Moose Development/Moose/Core/Database.lua | 27 ++ Moose Development/Moose/Core/Set.lua | 8 + .../Moose/Functional/Warehouse.lua | 8 +- Moose Development/Moose/Ops/ArmyGroup.lua | 220 ++++++++-- Moose Development/Moose/Ops/Auftrag.lua | 15 + Moose Development/Moose/Ops/Chief.lua | 3 +- Moose Development/Moose/Ops/Cohort.lua | 29 +- Moose Development/Moose/Ops/Commander.lua | 39 +- Moose Development/Moose/Ops/FlightGroup.lua | 28 +- Moose Development/Moose/Ops/Intelligence.lua | 31 +- Moose Development/Moose/Ops/Legion.lua | 18 + Moose Development/Moose/Ops/NavyGroup.lua | 6 +- Moose Development/Moose/Ops/OpsGroup.lua | 402 ++++++++++++++---- Moose Development/Moose/Ops/Target.lua | 93 +++- Moose Development/Moose/Wrapper/Group.lua | 7 +- 15 files changed, 774 insertions(+), 160 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 0b4fff4c7..5e2063a12 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1543,6 +1543,33 @@ function DATABASE:FindOpsGroup(groupname) return self.FLIGHTGROUPS[groupname] end +--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base for a given unit. +-- @param #DATABASE self +-- @param #string unitname Unit name. Can also be passed as UNIT object. +-- @return Ops.OpsGroup#OPSGROUP OPS group object. +function DATABASE:FindOpsGroupFromUnit(unitname) + + local unit=nil --Wrapper.Unit#UNIT + local groupname + + -- Get group and group name. + if type(unitname)=="string" then + unit=UNIT:FindByName(unitname) + else + unit=unitname + end + + if unit then + groupname=unit:GetGroup():GetName() + end + + if groupname then + return self.FLIGHTGROUPS[groupname] + else + return nil + end +end + --- Add a flight control to the data base. -- @param #DATABASE self -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 518d8ff62..1d2fe6f30 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -6393,6 +6393,14 @@ do -- SET_OPSGROUP -- Trigger Added event. self:Added(ObjectName, object) + end + + --- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index. + -- @param #SET_BASE self + -- @param Ops.OpsGroup#OPSGROUP Object Ops group + -- @return Core.Base#BASE The added BASE Object. + function SET_OPSGROUP:AddObject(Object) + self:Add(Object.groupname, Object) end --- Add a GROUP or OPSGROUP object to the set. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 7a44c2820..04e5728ba 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -349,6 +349,7 @@ -- * @{#WAREHOUSE.Attribute.GROUND_APC} Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets. -- * @{#WAREHOUSE.Attribute.GROUND_TRUCK} Unarmed ground vehicles, which has the DCS "Truck" attribute. -- * @{#WAREHOUSE.Attribute.GROUND_INFANTRY} Ground infantry assets. +-- * @{#WAREHOUSE.Attribute.GROUND_IFV} Ground infantry fighting vehicle. -- * @{#WAREHOUSE.Attribute.GROUND_ARTILLERY} Artillery assets. -- * @{#WAREHOUSE.Attribute.GROUND_TANK} Tanks (modern or old). -- * @{#WAREHOUSE.Attribute.GROUND_TRAIN} Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. @@ -1704,6 +1705,7 @@ WAREHOUSE.Descriptor = { -- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets. -- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute. -- @field #string GROUND_INFANTRY Ground infantry assets. +-- @field #string GROUND_IFV Ground infantry fighting vehicle. -- @field #string GROUND_ARTILLERY Artillery assets. -- @field #string GROUND_TANK Tanks (modern or old). -- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. @@ -1730,6 +1732,7 @@ WAREHOUSE.Attribute = { GROUND_APC="Ground_APC", GROUND_TRUCK="Ground_Truck", GROUND_INFANTRY="Ground_Infantry", + GROUND_IFV="Ground_IFV", GROUND_ARTILLERY="Ground_Artillery", GROUND_TANK="Ground_Tank", GROUND_TRAIN="Ground_Train", @@ -8352,9 +8355,10 @@ function WAREHOUSE:_GetAttribute(group) --- Ground --- -------------- -- Ground - local apc=group:HasAttribute("Infantry carriers") + local apc=group:HasAttribute("APC") --("Infantry carriers") local truck=group:HasAttribute("Trucks") and group:GetCategory()==Group.Category.GROUND local infantry=group:HasAttribute("Infantry") + local ifv=group:HasAttribute("IFV") local artillery=group:HasAttribute("Artillery") local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") local aaa=group:HasAttribute("AAA") @@ -8391,6 +8395,8 @@ function WAREHOUSE:_GetAttribute(group) attribute=WAREHOUSE.Attribute.AIR_UAV elseif apc then attribute=WAREHOUSE.Attribute.GROUND_APC + elseif ifv then + attribute=WAREHOUSE.Attribute.GROUND_IFV elseif infantry then attribute=WAREHOUSE.Attribute.GROUND_INFANTRY elseif artillery then diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 7102a7aa0..c40948931 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -34,6 +34,11 @@ -- @field #boolean isMobile If true, group is mobile. -- @field #ARMYGROUP.Target engage Engage target. -- @field Core.Set#SET_ZONE retreatZones Set of retreat zones. +-- @field #boolean suppressOn Bla +-- @field #boolean isSuppressed Bla +-- @field #number TsuppressMin Bla +-- @field #number TsuppressMax Bla +-- @field #number TsuppressAve Bla -- @extends Ops.OpsGroup#OPSGROUP --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge @@ -122,6 +127,9 @@ function ARMYGROUP:New(group) self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat. self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated. + self:AddTransition("*", "Suppressed", "*") -- Group is suppressed + self:AddTransition("*", "Unsuppressed", "*") -- Group is unsuppressed. + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state @@ -280,6 +288,7 @@ function ARMYGROUP:New(group) -- @function [parent=#ARMYGROUP] EngageTarget -- @param #ARMYGROUP self -- @param Wrapper.Group#GROUP Group the group to be engaged. + -- @param #number Speed Speed in knots. -- @param #string Formation Formation used in the engagement. --- Triggers the FSM event "EngageTarget" after a delay. @@ -287,6 +296,7 @@ function ARMYGROUP:New(group) -- @param #ARMYGROUP self -- @param #number delay Delay in seconds. -- @param Wrapper.Group#GROUP Group the group to be engaged. + -- @param #number Speed Speed in knots. -- @param #string Formation Formation used in the engagement. @@ -297,6 +307,7 @@ function ARMYGROUP:New(group) -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP Group the group to be engaged. + -- @param #number Speed Speed in knots. -- @param #string Formation Formation used in the engagement. @@ -386,7 +397,7 @@ function ARMYGROUP:New(group) self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) - --self:HandleEvent(EVENTS.Hit, self.OnEventHit) + self:HandleEvent(EVENTS.Hit, self.OnEventHit) -- Start the status monitoring. self.timerStatus=TIMER:New(self.Status, self):Start(1, 30) @@ -572,6 +583,47 @@ function ARMYGROUP:AddRetreatZone(RetreatZone) return self end +--- Set suppression on. average, minimum and maximum time a unit is suppressed each time it gets hit. +-- @param #ARMYGROUP self +-- @param #number Tave Average time [seconds] a group will be suppressed. Default is 15 seconds. +-- @param #number Tmin (Optional) Minimum time [seconds] a group will be suppressed. Default is 5 seconds. +-- @param #number Tmax (Optional) Maximum time a group will be suppressed. Default is 25 seconds. +-- @return #ARMYGROUP self +function ARMYGROUP:SetSuppressionOn(Tave, Tmin, Tmax) + + -- Activate suppression. + self.suppressionOn=true + + -- Minimum suppression time is input or default 5 sec (but at least 1 second). + self.TsuppressMin=Tmin or 1 + self.TsuppressMin=math.max(self.TsuppressMin, 1) + + -- Maximum suppression time is input or default but at least Tmin. + self.TsuppressMax=Tmax or 15 + self.TsuppressMax=math.max(self.TsuppressMax, self.TsuppressMin) + + -- Expected suppression time is input or default but at leat Tmin and at most Tmax. + self.TsuppressAve=Tave or 10 + self.TsuppressAve=math.max(self.TsuppressMin) + self.TsuppressAve=math.min(self.TsuppressMax) + + -- Debug Info + self:T(self.lid..string.format("Set ave suppression time to %d seconds.", self.TsuppressAve)) + self:T(self.lid..string.format("Set min suppression time to %d seconds.", self.TsuppressMin)) + self:T(self.lid..string.format("Set max suppression time to %d seconds.", self.TsuppressMax)) + + return self +end + +--- Set suppression off. +-- @param #ARMYGROUP self +-- @return #ARMYGROUP self +function ARMYGROUP:SetSuppressionOff() + -- Activate suppression. + self.suppressionOn=false +end + + --- Check if the group is currently holding its positon. -- @param #ARMYGROUP self -- @return #boolean If true, group was ordered to hold. @@ -651,10 +703,14 @@ function ARMYGROUP:Status() -- Check if group is waiting. if self:IsWaiting() then if self.Twaiting and self.dTwait then - if timer.getAbsTime()>self.Twaiting+self.dTwait then + if timer.getAbsTime()>self.Twaiting+self.dTwait then self.Twaiting=nil self.dTwait=nil - self:Cruise() + if self:_CountPausedMissions()>0 then + self:UnpauseMission() + else + self:Cruise() + end end end end @@ -799,22 +855,6 @@ end -- DCS Events ==> See OPSGROUP ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Event function handling when a unit is hit. --- @param #ARMYGROUP self --- @param Core.Event#EVENTDATA EventData Event data. -function ARMYGROUP:OnEventHit(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 - - -- TODO: suppression - - end -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Events ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1126,7 +1166,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) self.speedWp=wp.speed -- Debug output. - if self.verbose>=10 or true then + if self.verbose>=10 then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint @@ -1228,6 +1268,16 @@ end function ARMYGROUP:onafterOutOfAmmo(From, Event, To) self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime())) + -- Get current task. + local task=self:GetTaskCurrent() + + if task then + if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then + self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) + self:TaskCancel(task) + end + end + -- Fist, check if we want to rearm once out-of-ammo. --TODO: IsMobile() check if self.rearmOnOutOfAmmo then @@ -1250,16 +1300,6 @@ function ARMYGROUP:onafterOutOfAmmo(From, Event, To) if self.rtzOnOutOfAmmo then self:__RTZ(-1) end - - -- Get current task. - local task=self:GetTaskCurrent() - - if task then - if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then - self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id)) - self:TaskCancel(task) - end - end end @@ -1292,6 +1332,15 @@ function ARMYGROUP:onbeforeRearm(From, Event, To, Coordinate, Formation) allowed=false end + -- Check if coordinate is provided. + if allowed and not Coordinate then + local truck=self:FindNearestAmmoSupply() + if truck and truck:IsAlive() then + self:__Rearm(-0.1, truck:GetCoordinate(), Formation) + end + return false + end + -- Try again... if dt then self:T(self.lid..string.format("Trying Rearm again in %.2f sec", dt)) @@ -1589,7 +1638,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Formation self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) -- Get a coordinate close to the target. - local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9) + local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.95) -- Backup ROE and alarm state. self.engage.roe=self:GetROE() @@ -1752,6 +1801,21 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation) end +--- On after "Hit" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT Enemy Unit that hit the element or `nil`. +function ARMYGROUP:onafterHit(From, Event, To, Enemy) + self:T(self.lid..string.format("ArmyGroup hit by %s", Enemy and Enemy:GetName() or "unknown")) + + if self.suppressionOn then + env.info(self.lid.."FF suppress") + self:_Suppress() + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1996,6 +2060,100 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius) return nil, nil end +--- Suppress fire of the group by setting its ROE to weapon hold. +-- @param #ARMYGROUP self +function ARMYGROUP:_Suppress() + + -- Current time. + local Tnow=timer.getTime() + + -- Current ROE + local currROE=self:GetROE() + + + -- Get randomized time the unit is suppressed. + local sigma=(self.TsuppressMax-self.TsuppressMin)/4 + + -- Gaussian distribution. + local Tsuppress=UTILS.RandomGaussian(self.TsuppressAve,sigma,self.TsuppressMin, self.TsuppressMax) + + -- Time at which the suppression is over. + local renew=true + if not self.TsuppressionOver then + + -- Group is not suppressed currently. + self.TsuppressionOver=Tnow+Tsuppress + + -- Group will hold their weapons. + self:SwitchROE(ENUMS.ROE.WeaponHold) + + -- Backup ROE. + self.suppressionROE=currROE + + else + -- Check if suppression is longer than current time. + if Tsuppress+Tnow > self.TsuppressionOver then + self.TsuppressionOver=Tnow+Tsuppress + else + renew=false + end + end + + -- Recovery event will be called in Tsuppress seconds. + if renew then + self:__Unsuppressed(self.TsuppressionOver-Tnow) + end + + -- Debug message. + self:T(self.lid..string.format("Suppressed for %d sec", Tsuppress)) + +end + +--- Before "Recovered" event. Check if suppression time is over. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @return #boolean +function ARMYGROUP:onbeforeUnsuppressed(From, Event, To) + + -- Current time. + local Tnow=timer.getTime() + + -- Debug info + self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver)) + + -- Recovery is only possible if enough time since the last hit has passed. + if Tnow >= self.TsuppressionOver then + return true + else + return false + end + +end + +--- After "Recovered" event. Group has recovered and its ROE is set back to the "normal" unsuppressed state. Optionally the group is flared green. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterUnsuppressed(From, Event, To) + + -- Debug message. + local text=string.format("Group %s has recovered!", self:GetName()) + MESSAGE:New(text, 10):ToAll() + self:T(self.lid..text) + + -- Set ROE back to default. + self:SwitchROE(self.suppressionROE) + + -- Flare unit green. + if true then + self.group:FlareGreen() + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 2361a21d5..5142b87d1 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -2529,6 +2529,21 @@ function AUFTRAG:GetRequiredAssets(Legion) return Nmin, Nmax end +--- **[LEGION, COMMANDER, CHIEF]** Set that only alive (spawned) assets are considered. +-- @param #AUFTRAG self +-- @param #boolean Switch If true or nil, only active assets. If false +-- @return #AUFTRAG self +function AUFTRAG:SetAssetsStayAlive(Switch) + + if Switch==nil then + Switch=true + end + + self.assetStayAlive=Switch + + return self +end + --- **[LEGION, COMMANDER, CHIEF]** Define how many assets are required that escort the mission assets. -- Only used if the mission is handled by a **LEGION** (AIRWING, BRIGADE, FLEET) or higher level. -- @param #AUFTRAG self diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index ce5d5a65c..006fad90d 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -955,6 +955,7 @@ end function CHIEF:AddTarget(Target) if not self:IsTarget(Target) then + Target.chief=self table.insert(self.targetqueue, Target) end @@ -1536,7 +1537,7 @@ function CHIEF:onafterStatus(From, Event, To) for _,_target in pairs(self.targetqueue) do local target=_target --Ops.Target#TARGET - if target and target:IsAlive() and target.mission and target.mission:IsNotOver() then + if target and target:IsAlive() and target.chief and target.mission and target.mission:IsNotOver() then local inborder=self:CheckTargetInZones(target, self.borderzoneset) diff --git a/Moose Development/Moose/Ops/Cohort.lua b/Moose Development/Moose/Ops/Cohort.lua index fe90c4e2f..5b144c499 100644 --- a/Moose Development/Moose/Ops/Cohort.lua +++ b/Moose Development/Moose/Ops/Cohort.lua @@ -987,6 +987,31 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes) return N end +--- Get OPSGROUPs. +-- @param #COHORT self +-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types. +-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`. +-- @return Core.Set#SET_OPSGROUPS Ops groups set. +function COHORT:GetOpsGroups(MissionTypes, Attributes) + + local set=SET_OPSGROUP:New() + + for _,_asset in pairs(self.assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes, self.missiontypes) then + if Attributes==nil or self:CheckAttribute(Attributes) then + if asset.flightgroup and asset.flightgroup:IsAlive() then + --set:AddObject(asset.flightgroup) + set:AddGroup(asset.flightgroup) + end + end + end + end + + return set +end + --- Get assets for a mission. -- @param #COHORT self -- @param #string MissionType Mission type. @@ -1021,7 +1046,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads) if not (isRequested or isReserved) then -- Check if asset is currently on a mission (STARTED or QUEUED). - if self.legion:IsAssetOnMission(asset) then + if self.legion:IsAssetOnMission(asset) then --- -- Asset is already on a mission. --- @@ -1034,7 +1059,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads) elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.NOTHING) then - -- Relocation: Take all assets. Mission will be cancelled. + -- Assets on mission NOTHING are considered. table.insert(assets, asset) elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and MissionType==AUFTRAG.Type.INTERCEPT then diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 0ec0e9886..42296babe 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -837,7 +837,7 @@ function COMMANDER:onafterStatus(From, Event, To) -- Status. if self.verbose>=1 then - local text=string.format("Status %s: Legions=%d, Missions=%d, Transports", fsmstate, #self.legions, #self.missionqueue, #self.transportqueue) + local text=string.format("Status %s: Legions=%d, Missions=%d, Targets=%d, Transports=%d", fsmstate, #self.legions, #self.missionqueue, #self.targetqueue, #self.transportqueue) self:T(self.lid..text) end @@ -1031,6 +1031,21 @@ function COMMANDER:onafterStatus(From, Event, To) end self:I(self.lid..text) end + + + --- + -- TARGETS + --- + + -- Target queue. + if self.verbose>=2 and #self.targetqueue>0 then + local text="Target queue:" + for i,_target in pairs(self.targetqueue) do + local target=_target --Ops.Target#TARGET + text=text..string.format("\n[%d] %s: status=%s, life=%d", i, target:GetName(), target:GetState(), target:GetLife()) + end + self:I(self.lid..text) + end --- -- TRANSPORTS @@ -1214,11 +1229,25 @@ function COMMANDER:CheckTargetQueue() return nil end + -- Remove done targets. + for i=#self.targetqueue,1,-1 do + local target=self.targetqueue[i] --Ops.Target#TARGET + if (not target:IsAlive()) or target:EvalConditionsAny(target.conditionStop) then + for _,_resource in pairs(target.resources) do + local resource=_resource --Ops.Target#TARGET.Resource + if resource.mission and resource.mission:IsNotOver() then + self:MissionCancel(resource.mission) + end + end + table.remove(self.targetqueue, i) + end + end + -- Check if total number of missions is reached. local NoLimit=self:_CheckMissionLimit("Total") if NoLimit==false then return nil - end + end -- Sort results table wrt prio and threatlevel. local function _sort(a, b) @@ -1248,6 +1277,9 @@ function COMMANDER:CheckTargetQueue() -- Is this target important enough. local isImportant=(target.importance==nil or target.importance<=vip) + -- Check ALL start conditions are true. + local isReadyStart=target:EvalConditionsAll(target.conditionStart) + -- Debug message. local text=string.format("Target %s: Alive=%s, Threat=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isThreat), tostring(isImportant)) self:T2(self.lid..text) @@ -1270,10 +1302,13 @@ function COMMANDER:CheckTargetQueue() local mission=AUFTRAG:NewFromTarget(target, missionType) if mission then + + -- Set mission parameters. mission:SetRequiredAssets(resource.Nmin, resource.Nmax) mission:SetRequiredAttribute(resource.Attributes) mission:SetRequiredProperty(resource.Properties) + -- Set resource mission. resource.mission=mission -- Add mission to queue. diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ccd140c4a..8854055a4 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2231,10 +2231,23 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) self:T(self.lid.."Engaging! Group NOT done...") return end + + -- Number of tasks remaining. + local nTasks=self:CountRemainingTasks() - -- First check if there is a paused mission. - if self.missionpaused then - self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...", self.missionpaused.name, self.missionpaused.type)) + -- Number of mission remaining. + local nMissions=self:CountRemainingMissison() + + -- Number of cargo transports remaining. + local nTransports=self:CountRemainingTransports() + + -- Number of paused missions. + local nPaused=self:_CountPausedMissions() + + -- First check if there is a paused mission and that all remaining missions are paused. If there are other missions in the queue, we will run those. + if nPaused>0 and nPaused==nMissions then + local missionpaused=self:_GetPausedMission() + self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...", missionpaused.name, missionpaused.type)) self:UnpauseMission() return end @@ -2251,15 +2264,6 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) return end - -- Number of tasks remaining. - local nTasks=self:CountRemainingTasks() - - -- Number of mission remaining. - local nMissions=self:CountRemainingMissison() - - -- Number of cargo transports remaining. - local nTransports=self:CountRemainingTransports() - -- Debug info. self:T(self.lid..string.format("Remaining (final=%s): missions=%d, tasks=%d, transports=%d", tostring(self.passedfinalwp), nMissions, nTasks, nTransports)) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 28f91150d..28437a98e 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -1453,6 +1453,9 @@ function INTEL:PaintPicture() self:AddContactToCluster(contact, cluster) else + + -- Debug info. + self:T(self.lid..string.format("Paint Picture: contact %s has no closest cluster ==> Create new cluster", contact.groupname)) -- Create a brand new cluster. local newcluster=self:_CreateClusterFromContact(contact) @@ -1817,13 +1820,13 @@ function INTEL:IsContactConnectedToCluster(contact, cluster) --local dist=Contact.position:Get2DDistance(contact.position) local dist=Contact.position:DistanceFromPointVec2(contact.position) - -- AIR - check for spatial proximity - local airprox = false + -- AIR - check for spatial proximity (corrected because airprox was always false for ctype~=INTEL.Ctype.AIRCRAFT) + local airprox = true if contact.ctype == INTEL.Ctype.AIRCRAFT then self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,contact.altitude)) local adist = math.abs(cluster.altitude - contact.altitude) - if adist < UTILS.FeetToMeters(10000) then -- limit to 10kft - airprox = true + if adist > UTILS.FeetToMeters(10000) then -- limit to 10kft + airprox = false end end @@ -1903,17 +1906,17 @@ function INTEL:_GetClosestClusterOfContact(Contact) local dist=self:_GetDistContactToCluster(Contact, cluster) - -- AIR - check for spatial proximity - local airprox = false + -- AIR - check for spatial proximity (ff: Changed because airprox was always false for ctype~=AIRCRAFT!) + local airprox=true if Contact.ctype == INTEL.Ctype.AIRCRAFT then - if not cluster.altitude then - cluster.altitude = self:GetClusterAltitude(cluster,true) - end - local adist = math.abs(cluster.altitude - Contact.altitude) - self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,Contact.altitude)) - if adist < UTILS.FeetToMeters(10000) then - airprox = true - end + if not cluster.altitude then + cluster.altitude = self:GetClusterAltitude(cluster,true) + end + local adist = math.abs(cluster.altitude - Contact.altitude) + self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,Contact.altitude)) + if adist > UTILS.FeetToMeters(10000) then + airprox = false + end end if distself.Twaiting+self.dTwait then self.Twaiting=nil self.dTwait=nil - self:Cruise() + if self:_CountPausedMissions()>0 then + self:UnpauseMission() + else + self:Cruise() + end end end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index cbca93657..f76cf8f89 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -68,9 +68,10 @@ -- @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. +-- @field #table pausedmissions Paused missions. -- @field #number Ndestroyed Number of destroyed units. -- @field #number Nkills Number kills of this groups. +-- @field #number Nhit Number of hits taken. -- -- @field #boolean rearmOnOutOfAmmo If `true`, group will go to rearm once it runs out of ammo. -- @@ -185,6 +186,7 @@ OPSGROUP = { callsign = {}, Ndestroyed = 0, Nkills = 0, + Nhit = 0, weaponData = {}, cargoqueue = {}, cargoBay = {}, @@ -192,6 +194,7 @@ OPSGROUP = { carrierLoader = {}, carrierUnloader = {}, useMEtasks = false, + pausedmissions = {}, } @@ -206,6 +209,7 @@ OPSGROUP = { -- @field #boolean ai If true, element is AI. -- @field #string skill Skill level. -- @field #string playerName Name of player if this is a client. +-- @field #number Nhit Number of times the element was hit. -- -- @field Core.Zone#ZONE_POLYGON_BASE zoneBoundingbox Bounding box zone of the element unit. -- @field Core.Zone#ZONE_POLYGON_BASE zoneLoad Loading zone. @@ -646,8 +650,9 @@ function OPSGROUP:New(group) self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. + self:AddTransition("*", "Hit", "*") -- Someone in the group was hit. self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. + self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. @@ -706,6 +711,7 @@ function OPSGROUP:New(group) self:AddTransition("*", "ElementDestroyed", "*") -- An element was destroyed. self:AddTransition("*", "ElementDead", "*") -- An element is dead. self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. + self:AddTransition("*", "ElementHit", "*") -- An element was hit. self:AddTransition("*", "Board", "*") -- Group is ordered to board the carrier. self:AddTransition("*", "Embarked", "*") -- Group was loaded into a cargo carrier. @@ -1176,6 +1182,105 @@ function OPSGROUP:IsTargetDetected(TargetObject) return false end +--- Check if a given coordinate is in weapon range. +-- @param #OPSGROUP self +-- @param Core.Point#COORDINATE TargetCoord Coordinate of the target. +-- @param #number WeaponBitType Weapon type. +-- @param Core.Point#COORDINATE RefCoord Reference coordinate. +-- @return #boolean If `true`, coordinate is in range. +function OPSGROUP:InWeaponRange(TargetCoord, WeaponBitType, RefCoord) + + RefCoord=RefCoord or self:GetCoordinate() + + local dist=TargetCoord:Get2DDistance(RefCoord) + + if WeaponBitType then + + local weapondata=self:GetWeaponData(WeaponBitType) + + if weapondata then + + if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then + return true + else + return false + end + + end + + else + + for _,_weapondata in pairs(self.weaponData or {}) do + local weapondata=_weapondata --#OPSGROUP.WeaponData + + if dist>=weapondata.RangeMin and dist<=weapondata.RangeMax then + return true + end + + end + + return false + end + + + return nil +end + +--- Get a coordinate, which is in weapon range. +-- @param #OPSGROUP self +-- @param Core.Point#COORDINATE TargetCoord Coordinate of the target. +-- @param #number WeaponBitType Weapon type. +-- @param Core.Point#COORDINATE RefCoord Reference coordinate. +-- @return Core.Point#COORDINATE Coordinate in weapon range +function OPSGROUP:GetCoordinateInRange(TargetCoord, WeaponBitType, RefCoord) + + local coordInRange=nil --Core.Point#COORDINATE + + RefCoord=RefCoord or self:GetCoordinate() + + -- Get weapon range. + local weapondata=self:GetWeaponData(WeaponBitType) + + if weapondata then + + -- Heading to target. + local heading=RefCoord:HeadingTo(TargetCoord) + + -- Distance to target. + local dist=RefCoord:Get2DDistance(TargetCoord) + + -- Check if we are within range. + if dist>weapondata.RangeMax then + + local d=(dist-weapondata.RangeMax)*1.05 + + -- New waypoint coord. + coordInRange=RefCoord:Translate(d, heading) + + -- Debug info. + self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(WeaponBitType))) + elseif dist0 then + for _,mid in pairs(self.pausedmissions) do + if mid then + local mission=self:GetMissionByID(mid) + if mission and mission:IsNotOver() then + return mission + end + end + end + end + + return nil +end + +--- Count paused mission. +-- @param #OPSGROUP self +-- @return #number Number of paused missions. +function OPSGROUP:_CountPausedMissions() + local N=0 + if self.pausedmissions and #self.pausedmissions>0 then + for _,mid in pairs(self.pausedmissions) do + local mission=self:GetMissionByID(mid) + if mission and mission:IsNotOver() then + N=N+1 + end + end + end + + return N +end + +--- Remove paused mission from the table. +-- @param #OPSGROUP self +-- @param #number AuftragsNummer Mission ID of the paused mission to remove. +-- @return #OPSGROUP self +function OPSGROUP:_RemovePausedMission(AuftragsNummer) + + if self.pausedmissions and #self.pausedmissions>0 then + for i=#self.pausedmissions,1,-1 do + local mid=self.pausedmissions[i] + if mid==AuftragsNummer then + table.remove(self.pausedmissions, i) + return self + end + end + end + + return self +end + --- Check if the group is currently boarding a carrier. -- @param #OPSGROUP self -- @param #string CarrierGroupName (Optional) Additionally check if group is boarding this particular carrier group. @@ -3213,7 +3373,36 @@ function OPSGROUP:OnEventBirth(EventData) end ---- Event function handling the crash of a unit. +--- Event function handling the hit of a unit. +-- @param #OPSGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function OPSGROUP:OnEventHit(EventData) + + -- Check that this is the right group. Here the hit group is stored as target. + if EventData and EventData.TgtGroup and EventData.TgtUnit and EventData.TgtGroupName and EventData.TgtGroupName==self.groupname then + self:T2(self.lid..string.format("EVENT: Unit %s hit!", EventData.TgtUnitName)) + + local unit=EventData.TgtUnit + local group=EventData.TgtGroup + local unitname=EventData.TgtUnitName + + -- Get element. + local element=self:GetElementByName(unitname) + + -- Increase group hit counter. + self.Nhit=self.Nhit or 0 + self.Nhit=self.Nhit + 1 + + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + -- Trigger Element Hit Event. + self:ElementHit(element, EventData.IniUnit) + end + + end + +end + +--- Event function handling the dead of a unit. -- @param #OPSGROUP self -- @param Core.Event#EVENTDATA EventData Event data. function OPSGROUP:OnEventDead(EventData) @@ -4491,8 +4680,10 @@ function OPSGROUP:RemoveMission(Mission) end -- Take care of a paused mission. - if self.missionpaused and self.missionpaused.auftragsnummer==Mission.auftragsnummer then - self.missionpaused=nil + for j,mid in pairs(self.pausedmissions) do + if Mission.auftragsnummer==mid then + table.remove(self.pausedmission, j) + end end -- Remove mission from queue. @@ -4865,7 +5056,7 @@ function OPSGROUP:onafterPauseMission(From, Event, To) self:_RemoveMissionWaypoints(Mission) -- Set mission to pause so we can unpause it later. - self.missionpaused=Mission + table.insert(self.pausedmissions, 1, Mission.auftragsnummer) end @@ -4877,19 +5068,28 @@ end -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterUnpauseMission(From, Event, To) + + -- Get paused mission. + local mission=self:_GetPausedMission() - -- Debug info. - self:T(self.lid..string.format("Unpausing mission")) - - if self.missionpaused then - - local mission=self:GetMissionByID(self.missionpaused.auftragsnummer) - - if mission then - self:MissionStart(mission) + if mission then + + -- Debug info. + self:T(self.lid..string.format("Unpausing mission %s [%s]", mission:GetName(), mission:GetType())) + + -- Start mission. + self:MissionStart(mission) + + -- Remove mission from + for i,mid in pairs(self.pausedmissions) do + --self:T(self.lid..string.format("Checking paused mission", mid)) + if mid==mission.auftragsnummer then + self:T(self.lid..string.format("Removing paused mission id=%d", mid)) + table.remove(self.pausedmissions, i) + break + end end - - self.missionpaused=nil + else self:T(self.lid.."ERROR: No mission to unpause!") end @@ -4911,14 +5111,15 @@ function OPSGROUP:onafterMissionCancel(From, Event, To, Mission) -- Current Mission --- - -- Alert 5 missoins dont have a task set, which could be cancelled. + -- Some missions dont have a task set, which could be cancelled. if Mission.type==AUFTRAG.Type.ALERT5 or Mission.type==AUFTRAG.Type.ONGUARD or Mission.type==AUFTRAG.Type.ARMOREDGUARD or - Mission.type==AUFTRAG.Type.NOTHING or + --Mission.type==AUFTRAG.Type.NOTHING or Mission.type==AUFTRAG.Type.AIRDEFENSE or Mission.type==AUFTRAG.Type.EWR then + -- Trigger mission don task. self:MissionDone(Mission) return @@ -5143,10 +5344,6 @@ function OPSGROUP:RouteToMission(mission, delay) self:MissionExecute(mission) return end - - if self.speedMax<=3.6 or mission.teleport then - --self:ClearWaypoints() - end -- ID of current waypoint. local uid=self:GetWaypointCurrentUID() @@ -5157,8 +5354,6 @@ function OPSGROUP:RouteToMission(mission, delay) -- Current coordinate of the group. local currentcoord=self:GetCoordinate() - currentcoord:MarkToAll(mission:GetName(),ReadOnly,Text) - -- Road connection. local roadcoord=currentcoord:GetClosestPointToRoad() @@ -5181,12 +5376,9 @@ function OPSGROUP:RouteToMission(mission, delay) surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} end - -- Get ingress waypoint. if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname) then - --env.info(self.lid.."FF mission waypoint in embark zone") - -- Get transport zone combo. local tzc=mission.opstransport:GetTZCofCargo(self.groupname) @@ -5200,7 +5392,6 @@ function OPSGROUP:RouteToMission(mission, delay) else -- Get a random coordinate inside the pickup zone. waypointcoord=pickupzone:GetRandomCoordinate() - --waypointcoord:MarkToAll(self.lid.." embark here") end elseif mission.type==AUFTRAG.Type.PATROLZONE or @@ -5219,7 +5410,6 @@ function OPSGROUP:RouteToMission(mission, delay) -- Random coordinate. waypointcoord=targetzone:GetRandomCoordinate(nil , nil, surfacetypes) - waypointcoord:MarkToAll(mission:GetName(),ReadOnly,Text) elseif mission.type==AUFTRAG.Type.ONGUARD or mission.type==AUFTRAG.Type.ARMOREDGUARD then --- -- Guard @@ -5338,65 +5528,41 @@ function OPSGROUP:RouteToMission(mission, delay) -- ARTY --- - -- Coord - local coord=waypointcoord - - -- Get weapon range. - local weapondata=self:GetWeaponData(mission.engageWeaponType) - - local coordInRange=nil --Core.Point#COORDINATE - if weapondata then - - -- Get target coordinate. - local targetcoord=mission:GetTargetCoordinate() - - -- Heading to target. - local heading=coord:HeadingTo(targetcoord) - - -- Distance to target. - local dist=coord:Get2DDistance(targetcoord) - - -- Check if we are within range. - if dist>weapondata.RangeMax then - - local d=(dist-weapondata.RangeMax)*1.1 - - -- New waypoint coord. - coordInRange=coord:Translate(d, heading) - - -- Debug info. - self:T(self.lid..string.format("Out of max range = %.1f km for weapon %s", weapondata.RangeMax/1000, tostring(mission.engageWeaponType))) - elseif dist0 then @@ -6652,6 +6827,37 @@ function OPSGROUP:onafterElementDamaged(From, Event, To, Element) end +--- On after "ElementHit" 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. +-- @param Wrapper.Unit#UNIT Enemy Unit that hit the element or `nil`. +function OPSGROUP:onafterElementHit(From, Event, To, Element, Enemy) + + -- Increase element hit counter. + Element.Nhit=Element.Nhit+1 + + -- Debug message. + self:T(self.lid..string.format("Element hit %s by %s [n=%d, N=%d]", Element.name, Enemy and Enemy:GetName() or "unknown", Element.Nhit, self.Nhit)) + + -- Group was hit. + self:__Hit(-3, Enemy) + +end + +--- On after "Hit" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT Enemy Unit that hit the element or `nil`. +function OPSGROUP:onafterHit(From, Event, To, Enemy) + self:T(self.lid..string.format("Group hit by %s", Enemy and Enemy:GetName() or "unknown")) +end + + --- On after "ElementDestroyed" event. -- @param #OPSGROUP self -- @param #string From From state. @@ -6818,9 +7024,9 @@ function OPSGROUP:Teleport(Coordinate, Delay, NoPauseMission) if self:IsFlightgroup() then Template.route.points[1]=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, 300, true, nil, nil, "Spawnpoint") elseif self:IsArmygroup() then - Template.route.points[1]=Coordinate:WaypointGround() + Template.route.points[1]=Coordinate:WaypointGround(0) elseif self:IsNavygroup() then - Template.route.points[1]=Coordinate:WaypointNaval() + Template.route.points[1]=Coordinate:WaypointNaval(0) end -- Template units. @@ -6892,6 +7098,7 @@ function OPSGROUP:_Respawn(Delay, Template, Reset) -- Number of destroyed units. self.Ndestroyed=0 + self.Nhit=0 -- Check if group is currently alive. if self:IsAlive() then @@ -7192,6 +7399,8 @@ function OPSGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.Crash) self.currbase=nil + elseif self.isArmygroup then + self:UnHandleEvent(EVENTS.Hit) end for _,_mission in pairs(self.missionqueue) do @@ -9038,7 +9247,7 @@ function OPSGROUP:onafterUnloaded(From, Event, To, OpsGroupCargo) OpsGroupCargo:Returned() end - if OpsGroupCargo.missionpaused then + if self:_CountPausedMissions()>0 then OpsGroupCargo:UnpauseMission() end @@ -9617,10 +9826,14 @@ function OPSGROUP:_CheckGroupDone(delay) -- Number of cargo transports remaining. local nTransports=self:CountRemainingTransports() + + -- Number of paused missions. + local nPaused=self:_CountPausedMissions() - -- First check if there is a paused mission that - if self.missionpaused and nMissions==1 then - self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...", self.missionpaused.name, self.missionpaused.type)) + -- First check if there is a paused mission and that all remaining missions are paused. If there are other missions in the queue, we will run those. + if nPaused>0 and nPaused==nMissions then + local missionpaused=self:_GetPausedMission() + self:T(self.lid..string.format("Found paused mission %s [%s]. Unpausing mission...", missionpaused.name, missionpaused.type)) self:UnpauseMission() return end @@ -12413,7 +12626,8 @@ function OPSGROUP:_AddElementByName(unitname) element.gid=element.DCSunit:getNumber() element.uid=element.DCSunit:getID() --element.group=unit:GetGroup() - element.controller=element.DCSunit:getController() + element.controller=element.DCSunit:getController() + element.Nhit=0 element.opsgroup=self -- Skill etc. @@ -12431,7 +12645,7 @@ function OPSGROUP:_AddElementByName(unitname) element.categoryname=unit:GetCategoryName() element.typename=unit:GetTypeName() - + -- Describtors. --self:I({desc=element.descriptors}) -- Ammo. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index faba46a41..f74268b9f 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -37,6 +37,7 @@ -- @field Ops.Intelligence#INTEL.Contact contact Contact attached to this target. -- @field #boolean isDestroyed If true, target objects were destroyed. -- @field #table resources Resource list. +-- @field #table conditionStart Start condition functions. -- @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 @@ -65,7 +66,8 @@ TARGET = { Ndead = 0, elements = {}, casualties = {}, - threatlevel0 = 0 + threatlevel0 = 0, + conditionStart = {}, } @@ -318,6 +320,95 @@ function TARGET:SetImportance(Importance) return self end +--- Add start condition. +-- @param #TARGET self +-- @param #function ConditionFunction Function that needs to be true before the mission can be started. Must return a #boolean. +-- @param ... Condition function arguments if any. +-- @return #TARGET self +function TARGET:AddConditionStart(ConditionFunction, ...) + + local condition={} --Ops.Auftrag#AUFTRAG.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionStart, condition) + + return self +end + +--- Add stop condition. +-- @param #TARGET self +-- @param #function ConditionFunction Function that needs to be true before the mission can be started. Must return a #boolean. +-- @param ... Condition function arguments if any. +-- @return #TARGET self +function TARGET:AddConditionStop(ConditionFunction, ...) + + local condition={} --Ops.Auftrag#AUFTRAG.Condition + + condition.func=ConditionFunction + condition.arg={} + if arg then + condition.arg=arg + end + + table.insert(self.conditionStop, condition) + + return self +end + +--- Check if all given condition are true. +-- @param #TARGET self +-- @param #table Conditions Table of conditions. +-- @return #boolean If true, all conditions were true. Returns false if at least one condition returned false. +function TARGET:EvalConditionsAll(Conditions) + + -- Any stop condition must be true. + for _,_condition in pairs(Conditions or {}) do + local condition=_condition --Ops.Auftrag#AUFTRAG.Condition + + -- Call function. + local istrue=condition.func(unpack(condition.arg)) + + -- Any false will return false. + if not istrue then + return false + end + + end + + -- All conditions were true. + return true +end + + +--- Check if any of the given conditions is true. +-- @param #TARGET self +-- @param #table Conditions Table of conditions. +-- @return #boolean If true, at least one condition is true. +function TARGET:EvalConditionsAny(Conditions) + + -- Any stop condition must be true. + for _,_condition in pairs(Conditions or {}) do + local condition=_condition --Ops.Auftrag#AUFTRAG.Condition + + -- Call function. + local istrue=condition.func(unpack(condition.arg)) + + -- Any true will return true. + if istrue then + return true + end + + end + + -- No condition was true. + return false +end + --- Add mission type and number of required assets to resource. -- @param #TARGET self -- @param #string MissionType Mission Type. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 73a306509..5484e533a 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -187,6 +187,7 @@ GROUPTEMPLATE.Takeoff = { -- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets. -- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute. -- @field #string GROUND_INFANTRY Ground infantry assets. +-- @field #string GROUND_IFV Ground Infantry Fighting Vehicle. -- @field #string GROUND_ARTILLERY Artillery assets. -- @field #string GROUND_TANK Tanks (modern or old). -- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. @@ -213,6 +214,7 @@ GROUP.Attribute = { GROUND_APC="Ground_APC", GROUND_TRUCK="Ground_Truck", GROUND_INFANTRY="Ground_Infantry", + GROUND_IFV="Ground_IFV", GROUND_ARTILLERY="Ground_Artillery", GROUND_TANK="Ground_Tank", GROUND_TRAIN="Ground_Train", @@ -2378,13 +2380,14 @@ function GROUP:GetAttribute() --- Ground --- -------------- -- Ground - local apc=self:HasAttribute("Infantry carriers") + local apc=self:HasAttribute("APC") local truck=self:HasAttribute("Trucks") and self:GetCategory()==Group.Category.GROUND local infantry=self:HasAttribute("Infantry") local artillery=self:HasAttribute("Artillery") local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks") local aaa=self:HasAttribute("AAA") local ewr=self:HasAttribute("EWR") + local ifv=self:HasAttribute("IFV") local sam=self:HasAttribute("SAM elements") and (not self:HasAttribute("AAA")) -- Train local train=self:GetCategory()==Group.Category.TRAIN @@ -2432,6 +2435,8 @@ function GROUP:GetAttribute() attribute=GROUP.Attribute.GROUND_APC elseif infantry then attribute=GROUP.Attribute.GROUND_INFANTRY + elseif ifv then + attribute=GROUP.Attribute.GROUND_IFV elseif truck then attribute=GROUP.Attribute.GROUND_TRUCK elseif train then