diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 9c2e1be01..48b53d8ac 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1773,7 +1773,7 @@ WAREHOUSE.version="1.0.0" -- TODO: Add check if assets "on the move" are stationary. Can happen if ground units get stuck in buildings. If stationary auto complete transport by adding assets to request warehouse? Time? -- TODO: Optimize findpathonroad. Do it only once (first time) and safe paths between warehouses similar to off-road paths. --- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? +-- NOGO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Make more examples: ARTY, CAP, ... -- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. -- TODO: Handle the case when units of a group die during the transfer. @@ -2594,6 +2594,9 @@ end -- @param #table ParkingIDs Table of numbers. -- @return #WAREHOUSE self function WAREHOUSE:SetParkingIDs(ParkingIDs) + if type(ParkingIDs)~="table" then + ParkingIDs={ParkingIDs} + end self.parkingIDs=ParkingIDs return self end @@ -5168,7 +5171,14 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) for k,_ in pairs(self.queue) do self.queue[k]=nil end + for k,_ in pairs(self.stock) do + --self.stock[k]=nil + end + + for k=#self.stock,1,-1 do + --local asset=self.stock[k] --#WAREHOUSE.Assetitem + --self:AssetDead(asset, nil) self.stock[k]=nil end diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index 361ff5ffa..39b215c04 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -482,13 +482,34 @@ end -- @return #AIRWING self function AIRWING:AddPatrolPointCAP(Coordinate, Heading, LegLength, Altitude, Speed) - local cappoint=self:NewPatrolPoint(Coordinate, Heading, LegLength, Speed, Altitude, LegLength) + local cappoint=self:NewPatrolPoint(Coordinate, Heading, LegLength, Altitude, Speed) + + cappoint.coord:MarkToAll(string.format("CAP Point alt=%d", cappoint.altitude)) table.insert(self.pointsCAP, cappoint) return self end +--- Add a patrol Point for TANKER missions. +-- @param #AIRWING self +-- @param Core.Point#COORDINATE Coordinate Coordinate of the patrol point. +-- @param #number Heading Heading in degrees. +-- @param #number LegLength Length of race-track orbit in NM. +-- @param #number Altitude Orbit altitude in feet. +-- @param #number Speed Orbit speed in knots. +-- @return #AIRWING self +function AIRWING:AddPatrolPointTANKER(Coordinate, Heading, LegLength, Altitude, Speed) + + local cappoint=self:NewPatrolPoint(Coordinate, Heading, LegLength, Altitude, Speed) + + cappoint.coord:MarkToAll(string.format("Tanker Point alt=%d", cappoint.altitude)) + + table.insert(self.pointsTANKER, cappoint) + + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -583,10 +604,13 @@ function AIRWING:onafterStatus(From, Event, To) local payload=asset.payload and table.concat(asset.payload.missiontypes, ", ") or "None" text=text.." payload="..payload - text=text.." flight=" + text=text..", flight: " if asset.flightgroup and asset.flightgroup:IsAlive() then local status=asset.flightgroup:GetState() - local fuel=asset.flightgroup:IsFuelLow() + local fuelmin=asset.flightgroup:GetFuelMin() + local fuellow=asset.flightgroup:IsFuelLow() + + text=text..string.format("%s fuel=%d (low=%s)", status, fuelmin, tostring(fuellow)) else text=text.."N/A" end @@ -1123,6 +1147,27 @@ function AIRWING:onafterAssetDead(From, Event, To, asset, request) -- Remove asset from squadron same end +--- On after "Destroyed" event. Remove assets from squadrons. Stop squadrons. Remove airwing from wingcommander. +-- @param #AIRWING self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function AIRWING:onafterDestroyed(From, Event, To) + + for _,_squadron in pairs(self.squadrons) do + local squadron=_squadron --Ops.Squadron#SQUADRON + for _,_asset in pairs(squadron.assets) do + local asset=_asset + squadron:DelAsset(asset) + end + squadron:Stop() + end + + -- Call parent warehouse function first. + self:GetParent(self).onafterDestroyed(self, From, Event, To) + +end + --- On after "Request" event. -- @param #AIRWING self diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index f101ae5cc..d0fa50b04 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -31,18 +31,18 @@ -- @field #table DCStask DCS task structure. -- @field #number Ntargets Number of mission targets. -- --- @field Core.Point#COORDINATE waypointcoord Coordinate of the waypoint task. --- -- @field Core.Point#COORDINATE orbitCoord Coordinate where to orbit. -- @field #number orbitSpeed Orbit speed in m/s. -- @field #number orbitHeading Orbit heading in degrees. -- @field #number orbitLeg Length of orbit leg in meters. -- --- @field Core.Zone#ZONE_RADIUS engageZone *Circular* engagement zone. --- @field #table engageTargetTypes Table of target types that are engaged in the engagement zone. +-- @field #AUFTRAG.TargetData engageTarget Target data to engage. -- @field Core.Point#COORDINATE engageCoord Coordinate of target location. -- @field Core.Set#SET_GROUP engageTargetGroupset Set of target groups to attack. -- @field Core.Set#SET_UNIT engageTargetUnitset Set of target units to attack. +-- +-- @field Core.Zone#ZONE_RADIUS engageZone *Circular* engagement zone. +-- @field #table engageTargetTypes Table of target types that are engaged in the engagement zone. -- @field #number engageAltitude Engagement altitude in meters. -- @field #number engageDirection Engagement direction in degrees. -- @field #number engageQuantity Number of times a target is engaged. @@ -187,21 +187,41 @@ AUFTRAG.Status={ FAILED="failed", } ---- FlightStatus +--- Flight status. -- @type AUFTRAG.FlightStatus -- @field #string SCHEDULED Mission is scheduled in a FLIGHGROUP queue waiting to be started. -- @field #string STARTED Flightgroup started this mission but it is not executed yet. -- @field #string EXECUTING Flightgroup is executing this mission. +-- @field #string PAUSED Flightgroup has paused this mission, e.g. for refuelling. -- @field #string DONE Mission task of the flightgroup is done. -- @field #string CANCELLED Mission was cancelled. AUFTRAG.FlightStatus={ SCHEDULED="scheduled", STARTED="started", EXECUTING="executing", + PAUSED="paused", DONE="done", CANCELLED="cancelled", } +--- +-- @type AUFTRAG.TargetType +-- @field #string GROUP Group target. +-- @field #string UNIT Unit target. +-- @field #string STATIC Static target. +-- @field #string COORDINATE Target coordinates. +AUFTRAG.TargetType={ + GROUP="Group", + UNIT="Unit", + STATIC="Static", + COORDINATE="Coordinate", +} + +--- +-- @type AUFTRAG.TargetData +-- @field Wrapper.Positionable#POSITIONABLE Target Target Object. +-- @field #string Type Target type: "Group", "Unit", "Static", "Vec2" + --- Mission success. -- @type AUFTRAG.Success -- @field #string ENGAGED Target was engaged. @@ -329,6 +349,8 @@ function AUFTRAG:NewANTISHIP(TargetUnitSet) mission.missionTask=ENUMS.MissionTask.ANTISHIPSTRIKE + mission.optionROE=ENUMS.ROE.OpenFire + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -355,9 +377,9 @@ function AUFTRAG:NewORBIT(Coordinate, Speed, Heading, Leg, Altitude) auftrag.orbitCoord.y=UTILS.FeetToMeters(Altitude) end - auftrag.missionAltitude=auftrag.orbitCoord.y*0.9 - - auftrag.missionFraction=0.9 + auftrag.missionAltitude=auftrag.orbitCoord.y*0.9 + auftrag.missionFraction=0.9 + auftrag.optionROE=ENUMS.ROE.ReturnFire auftrag.DCStask=auftrag:GetDCSMissionTask() @@ -400,10 +422,11 @@ function AUFTRAG:NewTANKER(OrbitCoordinate, OrbitSpeed, Heading, Leg, Altitude, -- Mission type PATROL. mission.type=AUFTRAG.Type.TANKER - mission.missionTask=ENUMS.MissionTask.REFUELING - mission.refuelSystem=RefuelSystem + mission.missionTask=ENUMS.MissionTask.REFUELING + mission.optionROE=ENUMS.ROE.WeaponHold + mission.DCStask=mission:GetDCSMissionTask() return mission @@ -425,7 +448,8 @@ function AUFTRAG:NewAWACS(OrbitCoordinate, OrbitSpeed, Heading, Leg, Altitude) -- Mission type PATROL. mission.type=AUFTRAG.Type.AWACS - mission.missionTask=ENUMS.MissionTask.AWACS + mission.missionTask=ENUMS.MissionTask.AWACS + mission.optionROE=ENUMS.ROE.WeaponHold mission.DCStask=mission:GetDCSMissionTask() @@ -436,18 +460,17 @@ end --- Create an INTERCEPT mission. -- @param #AUFTRAG self --- @param Core.Set#SET_GROUP TargetGroupSet The set of target groups to intercept. Can also be passed as a simple @{Wrapper.Group#GROUP} object. +-- @param Wrapper.Positionable#POSITIONABLE Target The target to intercept. Can also be passed as simple @{Wrapper.Group#GROUP} or @{Wrapper.Unit#UNIT} object. -- @return #AUFTRAG self -function AUFTRAG:NewINTERCEPT(TargetGroupSet) +function AUFTRAG:NewINTERCEPT(Target) - if TargetGroupSet:IsInstanceOf("GROUP") then - TargetGroupSet=SET_GROUP:New():AddGroup(TargetGroupSet) - end - local mission=AUFTRAG:New(AUFTRAG.Type.INTERCEPT) + + mission.engageTarget=self:_TargetFromObject(Target) - mission.engageTargetGroupset=TargetGroupSet - mission.engageTargetGroupset:FilterDeads():FilterCrashes() + mission.missionTask=ENUMS.MissionTask.INTERCEPT + mission.missionFraction=0.1 + mission.optionROE=ENUMS.ROE.OpenFire mission.DCStask=mission:GetDCSMissionTask() @@ -479,11 +502,12 @@ function AUFTRAG:NewCAP(OrbitCoordinate, OrbitSpeed, Heading, Leg, ZoneCAP, Targ mission.type=AUFTRAG.Type.CAP mission.engageZone=ZoneCAP or ZONE_RADIUS:New("CAP Zone", OrbitCoordinate:GetVec2(), mission.orbitLeg) mission.engageTargetTypes=TargetTypes or {"Air"} + + mission.missionTask=ENUMS.MissionTask.CAP + mission.optionROE=ENUMS.ROE.WeaponFree mission.DCStask=mission:GetDCSMissionTask() - mission.missionTask=ENUMS.MissionTask.CAP - return mission end @@ -512,47 +536,48 @@ function AUFTRAG:NewCAS(OrbitCoordinate, OrbitSpeed, Heading, Leg, ZoneCAS, Targ mission.type=AUFTRAG.Type.CAS mission.engageZone=ZoneCAS or ZONE_RADIUS:New("CAS Zone", OrbitCoordinate:GetVec2(), Leg) mission.engageTargetTypes=TargetTypes or {"Helicopters", "Ground Units", "Light armed ships"} + + mission.missionTask=ENUMS.MissionTask.CAS + mission.optionROE=ENUMS.ROE.WeaponFree mission.DCStask=mission:GetDCSMissionTask() - mission.missionTask=ENUMS.MissionTask.CAS - return mission end + + --- Create a BAI mission. -- @param #AUFTRAG self --- @param Core.Set#SET_Unit TargetGroupSet The set of target groups to attack. Instead of a SET an ordinary GROUP object can also be given if only a single group needs to be attacked. +-- @param Wrapper.Positionable#POSITIONABLE Target The target to attack. Can be a GROUP, UNIT or STATIC object. -- @return #AUFTRAG self -function AUFTRAG:NewBAI(TargetGroupSet) +function AUFTRAG:NewBAI(Target) - if TargetGroupSet:IsInstanceOf("GROUP") then - TargetGroupSet=SET_GROUP:New():AddGroup(TargetGroupSet) - end - local mission=AUFTRAG:New(AUFTRAG.Type.BAI) - mission.engageTargetUnitset=TargetGroupSet - mission.engageTargetUnitset:FilterDeads() + mission.engageTarget=self:_TargetFromObject(Target) - mission.missionTask=ENUMS.MissionTask.GROUNDATTACK + --mission.engageTargetUnitset=TargetGroupSet + --mission.engageTargetUnitset:FilterDeads() - mission.engageWeaponType=2956984318 + mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG --2956984318 mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - mission.engageAsGroup=true + mission.missionTask=ENUMS.MissionTask.GROUNDATTACK + mission.missionAltitude=nil mission.missionFraction=0.75 + mission.optionROE=ENUMS.ROE.OpenFire mission.DCStask=mission:GetDCSMissionTask() return mission end ---- Create a STRIKE mission. Flight will attack a specified coordinate. +--- Create a STRIKE mission. Flight will attack the closest map object to the specified coordinate. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE TargetCoordinate Target coordinate. --- @param #number Altitude Engage altitude in feet. Default 1000. +-- @param #number Altitude Engage altitude in feet. Default 1000 ft. -- @return #AUFTRAG self function AUFTRAG:NewSTRIKE(TargetCoordinate, Altitude) @@ -560,12 +585,13 @@ function AUFTRAG:NewSTRIKE(TargetCoordinate, Altitude) mission.engageCoord=TargetCoordinate mission.engageAltitude=UTILS.FeetToMeters(Altitude or 1000) - + mission.engageWeaponType=ENUMS.WeaponFlag.AnyAG --2956984318 + mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL mission.missionTask=ENUMS.MissionTask.GROUNDATTACK - - mission.engageWeaponType=2956984318 - mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL + mission.missionAltitude=mission.engageAltitude + mission.missionFraction=0.75 + mission.optionROE=ENUMS.ROE.OpenFire mission.DCStask=mission:GetDCSMissionTask() @@ -581,30 +607,31 @@ function AUFTRAG:NewBOMBING(TargetCoordinate, Altitude) local mission=AUFTRAG:New(AUFTRAG.Type.BOMBING) + -- DCS task options: mission.engageCoord=TargetCoordinate mission.engageAltitude=UTILS.FeetToMeters(Altitude or 25000) - - mission.missionAltitude=mission.engageAltitude - - mission.missionFraction=0.3 - mission.engageWeaponType=4030478 --2956984318 --ENUMS.WeaponFlag.AnyBomb mission.engageWeaponExpend=AI.Task.WeaponExpend.ALL - - mission.DCStask=mission:GetDCSMissionTask() - + + -- Mission options: mission.missionTask=ENUMS.MissionTask.GROUNDATTACK + mission.missionAltitude=mission.engageAltitude*0.8 + mission.missionFraction=0.3 + mission.optionROE=ENUMS.ROE.OpenFire + + -- Get DCS task. + mission.DCStask=mission:GetDCSMissionTask() return mission end ---- Create an ESCORT mission. Flight will escort another group and automatically engage certain target types. +--- 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 #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. Set to {} for a simple "FOLLOW" mission. +-- @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 function AUFTRAG:NewESCORT(EscortGroup, OffsetVector, EngageMaxDistance, TargetTypes) @@ -615,11 +642,13 @@ function AUFTRAG:NewESCORT(EscortGroup, OffsetVector, EngageMaxDistance, TargetT mission.engageMaxDistance=EngageMaxDistance mission.engageTargetTypes=TargetTypes or {"Air"} + mission.missionTask=ENUMS.MissionTask.ESCORT mission.missionFraction=0.1 - mission.DCStask=mission:GetDCSMissionTask() + -- TODO: what's the best ROE here? + mission.optionROE=ENUMS.ROE.OpenFire - mission.missionTask=ENUMS.MissionTask.ESCORT + mission.DCStask=mission:GetDCSMissionTask() return mission end @@ -667,6 +696,9 @@ function AUFTRAG:NewAUTO(EngageGroup) --TODO: ANTISHIP + mission=AUFTRAG:NewANTISHIP(group) + + --[[ local TargetUnitSet=SET_UNIT:New() for _,_unit in pairs(group:GetUnits()) do @@ -679,6 +711,7 @@ function AUFTRAG:NewAUTO(EngageGroup) if TargetUnitSet:Count()>0 then mission=AUFTRAG:NewANTISHIP(TargetUnitSet) end + ]] end end @@ -1467,6 +1500,24 @@ end function AUFTRAG:CountMissionTargets() local N=0 + + if self.engageTarget then + if self.engageTarget.Type==AUFTRAG.TargetType.GROUP then + local target=self.engageTarget.Target --Wrapper.Group#GROUP + N=N+target:CountAliveUnits() + elseif self.engageTarget.Type==AUFTRAG.TargetType.UNIT then + if self.engageTarget.Target and self.engageTarget.Target:IsAlive() then + N=N+1 + end + elseif self.engageTarget.Type==AUFTRAG.TargetType.GROUP then + local target=self.engageTarget.Target --Wrapper.Static#STATIC + if target and target:IsAlive() then + N=N+1 + end + end + end + + --[[ if self.engageTargetGroupset then --local n=self.engageTargetGroupset:CountAlive() @@ -1491,6 +1542,8 @@ function AUFTRAG:CountMissionTargets() N=N+n end + ]] + return N end @@ -1513,6 +1566,17 @@ end -- @return Core.Point#COORDINATE The target coordinate or nil. function AUFTRAG:GetTargetCoordinate() + if self.engageTarget then + return self.engageTarget.Target:GetCoordinate() + elseif self.engageCoord then + return self.engageCoord + elseif self.orbitCoord then + return self.orbitCoord + elseif self.escortGroup then + return self.escortGroup:GetCoordinate() + end + + --[[ if self.engageTargetGroupset then local group=self.engageTargetGroupset:GetFirst() --Wrapper.Group#GROUP if group and group:IsAlive() then @@ -1530,7 +1594,8 @@ function AUFTRAG:GetTargetCoordinate() elseif self.escortGroup then return self.escortGroup:GetCoordinate() end - + ]] + return nil end @@ -1573,15 +1638,9 @@ function AUFTRAG:GetDCSMissionTask() -- ANTISHIP Mission -- ---------------------- - for _,_unit in pairs(self.engageTargetUnitset:GetSet()) do - local TargetUnit=_unit - - local DCStask=CONTROLLABLE.TaskAttackUnit(nil, TargetUnit, self.engageAltitude, self.WeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) - local DCStask=CONTROLLABLE.TaskAttackUnit(self,AttackUnit,GroupAttack,WeaponExpend,AttackQty,Direction,Altitude,WeaponType) - - table.insert(DCStasks, DCStask) - - end + local DCStask=self:_GetDCSAttackTask(self.engageTarget) + + table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.AWACS then @@ -1600,25 +1659,10 @@ function AUFTRAG:GetDCSMissionTask() -- BAI Mission -- ----------------- - --[[ - for _,_group in pairs(self.engageTargetGroupset:GetSet()) do - local TargetGroup=_group - - local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude) - local DCSTask= - - table.insert(DCStasks, DCStask) - end - ]] + -- TODO: adjust tasks for group/unit attack! group attack also seems to now support the attack as group value. however, statics are only possible if attackunit. + local DCStask=self:_GetDCSAttackTask(self.engageTarget) - for _,_unit in pairs(self.engageTargetUnitset:GetSet()) do - local TargetUnit=_unit - - local DCStask=CONTROLLABLE.TaskAttackUnit(self, TargetUnit, self.engageAsGroup, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude, self.engageWeaponType) - - table.insert(DCStasks, DCStask) - - end + table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.BOMBING then @@ -1686,13 +1730,9 @@ function AUFTRAG:GetDCSMissionTask() -- INTERCEPT Mission -- ----------------------- - for _,group in pairs(self.engageTargetGroupset:GetSet()) do - local TargetGroup=group --Wrapper.Group#GROUP - - local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, self.engageWeaponType, self.engageWeaponExpend, self.engageQuantity, self.engageDirection, self.engageAltitude) - - table.insert(DCStasks, DCStask) - end + local DCStask=self:_GetDCSAttackTask(self.engageTarget) + + table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.ORBIT then @@ -1715,6 +1755,16 @@ function AUFTRAG:GetDCSMissionTask() ------------------- -- RECON Mission -- ------------------- + + elseif self.type==AUFTRAG.Type.SEAD then + + ------------------ + -- SEAD Mission -- + ------------------ + + local DCStask=self:_GetDCSAttackTask(self.engageTarget) + + table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.STRIKE then @@ -1785,6 +1835,62 @@ function AUFTRAG:GetDCSMissionTask() end +--- Get DCS task table for an attack task. +-- @param #AUFTRAG self +-- @param #AUFTRAG.TargetData target Target data. +-- @return DCS#Task The DCS task table. +function AUFTRAG:_GetDCSAttackTask(target) + + local DCStask=nil + + 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) + + 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) + + end + + return DCStask +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 + + elseif Object:IsInstanceOf("UNIT") then + + target.Type=AUFTRAG.TargetType.UNIT + + elseif Object:IsInstanceOf("STATIC") then + + target.Type=AUFTRAG.TargetType.STATIC + + elseif Object:IsInstanceOf("COORDINATE") then + + target.Type=AUFTRAG.TargetType.COORDINATE + + else + self:E(self.lid.."ERROR: Unknown object given as target. Needs to be a GROUP, UNIT, STATIC or COORDINATE") + return nil + end + + return target +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f818b68a7..e75381292 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -18,6 +18,7 @@ -- @type FLIGHTGROUP -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #number verbose Verbosity level. 0=silent. -- @field #string lid Class id string for output to DCS log file. -- @field #string groupname Name of flight group. -- @field Wrapper.Group#GROUP group Flight group object. @@ -72,10 +73,12 @@ -- @field #number CallsignNumber Call sign number. -- @field #boolean EPLRS If true, turn EPLRS data link on. -- --- @field #string ROEdefault Default ROE setting. --- @field #string ROTdefault Default ROT setting. --- @field #string ROEcurrent Current ROE setting. --- @field #string ROTcurrent Current ROT setting. +-- @field #string optionROEdefault Default ROE setting. +-- @field #string optionROTdefault Default ROT setting. +-- @field #string optionROEcurrent Current ROE setting. +-- @field #string optionROTcurrent Current ROT setting. +-- +-- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- -- @extends Core.Fsm#FSM @@ -166,6 +169,7 @@ FLIGHTGROUP = { ClassName = "FLIGHTGROUP", Debug = false, + verbose = 0, lid = nil, groupname = nil, group = nil, @@ -351,7 +355,7 @@ FLIGHTGROUP.ROT={ --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.3.6" +FLIGHTGROUP.version="0.3.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -412,6 +416,8 @@ function FLIGHTGROUP:New(groupname, autostart) -- Defaults self:SetFuelLowThreshold() self:SetFuelCriticalThreshold() + self:SetDefaultROE() + self:SetDefaultROT() -- Add FSM transitions. @@ -463,6 +469,8 @@ function FLIGHTGROUP:New(groupname, autostart) self:AddTransition("*", "MissionStart", "*") -- Mission is started. self:AddTransition("*", "MissionExecute", "*") -- Mission execution began. self:AddTransition("*", "MissionCancel", "*") -- Cancel current mission. + self:AddTransition("*", "PauseMission", "*") -- Pause the current mission. + self:AddTransition("*", "UnpauseMission", "*") -- Unpause the the paused mission. self:AddTransition("*", "MissionDone", "*") -- Mission is over. self:AddTransition("Airborne", "EngageTargets", "Engaging") -- Engage targets. @@ -581,16 +589,16 @@ function FLIGHTGROUP:AddTask(task, clock, description, prio, duration) -- Task data structure. local newtask={} --#FLIGHTGROUP.Task - newtask.description=description newtask.status=FLIGHTGROUP.TaskStatus.SCHEDULED newtask.dcstask=task + newtask.description=description or task.id newtask.prio=prio or 50 newtask.time=time newtask.id=self.taskcounter newtask.duration=duration newtask.waypoint=-1 newtask.type=FLIGHTGROUP.TaskType.SCHEDULED - newtask.stopflag=USERFLAG:New(string.format("StopTaskFlag %d", newtask.id)) + newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id)) newtask.stopflag:Set(0) -- Add to table. @@ -627,7 +635,7 @@ function FLIGHTGROUP:AddTaskWaypoint(task, waypointindex, description, prio, dur newtask.time=0 newtask.waypoint=waypointindex or (self.currentwp and self.currentwp+1 or 2) newtask.type=FLIGHTGROUP.TaskType.WAYPOINT - newtask.stopflag=USERFLAG:New(string.format("StopTaskFlag %d", newtask.id)) + newtask.stopflag=USERFLAG:New(string.format("%s StopTaskFlag %d", self.groupname, newtask.id)) newtask.stopflag:Set(0) -- Add to table. @@ -976,11 +984,11 @@ function FLIGHTGROUP:IsHolding() return self:Is("Holding") end ---- Check if flight is refueling. +--- Check if flight is going for fuel. -- @param #FLIGHTGROUP self -- @return #boolean If true, flight is refueling. -function FLIGHTGROUP:IsRefueling() - return self:Is("Refueling") +function FLIGHTGROUP:IsGoing4Fuel() + return self:Is("Going4Fuel") end --- Check if helo(!) flight is ordered to land at a specific point. @@ -1045,10 +1053,10 @@ function FLIGHTGROUP:Activate(delay) if self:IsAlive()==false then if delay then - self:I(self.lid..string.format("Activating late activated group in %d seconds", delay)) + self:T2(self.lid..string.format("Activating late activated group in %d seconds", delay)) self:ScheduleOnce(delay, FLIGHTGROUP.Activate, self) else - self:I(self.lid.."Activating late activated group") + self:T(self.lid.."Activating late activated group") self.group:Activate() end else @@ -1066,7 +1074,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) else self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive (yet)!") @@ -1081,7 +1089,25 @@ end -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetTask(DCSTask) if self:IsAlive() then - self.group:SetTask(DCSTask, 1) + + -- Inject enroute tasks. + if self.taskenroute and #self.taskenroute>0 then + if tostring(DCSTask.id)=="ComboTask" then + for _,task in pairs(self.taskenroute) do + table.insert(DCSTask.params.tasks, 1, task) + end + else + local tasks=UTILS.DeepCopy(self.taskenroute) + table.insert(tasks, DCSTask) + + DCSTask=self.group.TaskCombo(self, tasks) + end + end + + -- Set task + self.group:SetTask(DCSTask) + + -- Debug info. local text=string.format("SETTING Task %s", tostring(DCSTask.id)) if tostring(DCSTask.id)=="ComboTask" then for i,task in pairs(DCSTask.params.tasks) do @@ -1138,7 +1164,7 @@ function FLIGHTGROUP:Route(waypoints) -- Enroute tasks. if self.taskenroute then for _,TaskEnroute in pairs(self.taskenroute) do - table.insert(Tasks, TaskEnroute) + --table.insert(Tasks, TaskEnroute) end end @@ -1227,7 +1253,7 @@ function FLIGHTGROUP:onafterStart(From, Event, To) if group:IsAlive() then -- Debug info. - self:I(self.lid..string.format("Found EXISTING and ALIVE group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements)) + self:T(self.lid..string.format("Found EXISTING and ALIVE group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements)) -- Trigger spawned event for all elements. for _,element in pairs(self.elements) do @@ -1237,7 +1263,7 @@ function FLIGHTGROUP:onafterStart(From, Event, To) else -- Debug info. - self:I(self.lid..string.format("Found EXISTING but LATE ACTIVATED group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements)) + self:T(self.lid..string.format("Found EXISTING but LATE ACTIVATED group %s at start with %d/%d units/elements", group:GetName(), #units, #self.elements)) end end @@ -1343,7 +1369,9 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) 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", 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:I(self.lid..text) + if self.verbose>0 then + self:I(self.lid..text) + end -- Element status. text="Elements:" @@ -1374,7 +1402,9 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) if #self.elements==0 then text=text.." none!" end - self:I(self.lid..text) + if self.verbose>1 then + self:I(self.lid..text) + end --- -- Tasks @@ -1408,7 +1438,7 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) 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 - if #self.taskqueue>0 then + if #self.taskqueue>0 and self.verbose>1 then self:I(self.lid..text) end @@ -1418,10 +1448,9 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) -- Current mission name. local Mission=self:GetMissionByID(self.currentmission) - local mymission=Mission and Mission.name or "none" -- Current status. - local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), mymission) + 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) @@ -1429,7 +1458,9 @@ function FLIGHTGROUP:onafterFlightStatus(From, Event, To) text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d targets=%d", i, tostring(mission.name), mission.type, mission:GetFlightStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, mission:CountMissionTargets()) end - self:I(self.lid..text) + if self.verbose>0 then + self:I(self.lid..text) + end --- -- Fuel State @@ -1779,7 +1810,7 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) else - self:T2(self.lid..string.format("EVENT: Element %s shut down engines but is NOT alive ==> waiting for crash event (==> dead)", element.name)) + self:I(self.lid..string.format("EVENT: Element %s shut down engines but is NOT alive ==> waiting for crash event (==> dead)", element.name)) end @@ -1931,7 +1962,7 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) end -- Debug info. - self:I(self.lid..string.format("Element taxiing %s. Parking spot %s is now free", Element.name, TerminalID)) + self:T(self.lid..string.format("Element taxiing %s. Parking spot %s is now free", Element.name, TerminalID)) -- Not parking any more. Element.parking=nil @@ -1993,7 +2024,7 @@ end -- @param Wrapper.Airbase#AIRBASE airbase The airbase, where the element arrived. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Parking The Parking spot the element has. function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking) - self:I(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99)) + self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99)) -- Get parking spot @@ -2005,7 +2036,7 @@ function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Pa if Parking then if self.flightcontrol then local spot=self.flightcontrol.parking[Parking.TerminalID] --Wrapper.Airbase#AIRBASE.ParkingSpot - self:I(self.lid..string.format("Element arrived %s at %s on parking spot %s reserved for %s", Element.name, Parking.AirbaseName, Parking.TerminalID, tostring(spot.Reserved))) + self:T(self.lid..string.format("Element arrived %s at %s on parking spot %s reserved for %s", Element.name, Parking.AirbaseName, Parking.TerminalID, tostring(spot.Reserved))) if spot.Reserved then if spot.Reserved==Element.name then spot.Reserved=nil @@ -2027,7 +2058,7 @@ end -- @param #string To To state. -- @param #FLIGHTGROUP.Element Element The flight group element. function FLIGHTGROUP: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)) -- Not parking any more. Element.parking=nil @@ -2045,10 +2076,19 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterFlightSpawned(From, Event, To) self:T(self.lid..string.format("Flight spawned!")) + -- F10 menu. if self.ai then + -- Set default options. + self:SetOptionROE() + self:SetOptionROT() + + self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true) + self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) + 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) @@ -2345,7 +2385,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 @@ -2373,7 +2413,7 @@ end -- @param #table Template The template used to respawn the group. function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) - self:I(self.lid.."Respawning group!") + self:T(self.lid.."Respawning group!") local template=UTILS.DeepCopy(Template or self.template) @@ -2394,7 +2434,7 @@ end -- @param #number N Final waypoint number. function FLIGHTGROUP:onafterPassingWaypoint(From, Event, To, n, N) local text=string.format("Flight passed waypoint %d/%d", n, N) - self:I(self.lid..text) + self:T(self.lid..text) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) -- Get all waypoint tasks. @@ -2437,7 +2477,8 @@ function FLIGHTGROUP:onafterPassingWaypoint(From, Event, To, n, N) -- Execute waypoint tasks. if #taskswp>0 then - self:PushTask(self.group:TaskCombo(taskswp)) + --self:PushTask(self.group:TaskCombo(taskswp)) + self:SetTask(self.group:TaskCombo(taskswp)) end -- Final AIR waypoint reached? @@ -2613,7 +2654,7 @@ 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:I(self.lid..text) + self:T(self.lid..text) -- Holding points. @@ -2762,7 +2803,7 @@ 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:I(self.lid..text) + self:T(self.lid..text) --TODO: set ROE passive. introduce roe event/state/variable. @@ -2791,24 +2832,23 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) --TODO: cancel current task + self:PauseMission() - -- Orbit task. + -- Refueling task. local TaskRefuel=self.group:TaskRefueling() local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedRefuelling", self) local DCSTasks={TaskRefuel, TaskFunction} - local Speed=UTILS.KnotsToKmph(500) + local Speed=UTILS.KnotsToKmph(300) - local wp0=self.group:GetCoordinate():WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORINATE.WaypointAction.TurningPoint, Speed, true) - local wp9=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, "Refuel") - - --local TaskCombo=self.group:TaskCombo({TaskFunction, TaskRefuel, TaskFunction}) + local coordinate=self.group:GetCoordinate() + + Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5), self.group:GetHeading(), true) + + 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}) - - -- Set task. - --self:SetTask(TaskCombo) - --self:PushTask(TaskCombo) end @@ -2823,7 +2863,11 @@ function FLIGHTGROUP:onafterRefueled(From, Event, To) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) - self:__UpdateRoute(-1) + if self.missionpaused then + self:UnpauseMission() + else + self:__UpdateRoute(-1) + end end @@ -2908,7 +2952,7 @@ end -- @param #string To To state. -- @param Wrapper.Unit#UNIT Unit The detected unit. function FLIGHTGROUP:onafterDetectedUnit(From, Event, To, Unit) - self:T(self.lid..string.format("Detected unit %s", Unit:GetName())) + self:T2(self.lid..string.format("Detected unit %s", Unit:GetName())) self.detectedunits:AddUnit(Unit) end @@ -2987,7 +3031,7 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) -- Route helo back home. It is respawned! But this is the only way to ensure that it actually lands at the airbase. local airbase=self.destbase or self.homebase - if airbase and self.fuelcriticalrtb then + if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel() then self:RTB(airbase) --TODO: RTZ end @@ -3002,7 +3046,7 @@ end -- @param Core.Zone#ZONE Zone The zone that the group entered. function FLIGHTGROUP:onafterEnterZone(From, Event, To, Zone) local zonename=Zone and Zone:GetName() or "unknown" - self:I(self.lid..string.format("Entered Zone %s", zonename)) + self:T2(self.lid..string.format("Entered Zone %s", zonename)) self.inzones:Add(Zone:GetName(), Zone) end @@ -3014,7 +3058,7 @@ end -- @param Core.Zone#ZONE Zone The zone that the group entered. function FLIGHTGROUP:onafterLeaveZone(From, Event, To, Zone) local zonename=Zone and Zone:GetName() or "unknown" - self:I(self.lid..string.format("Left Zone %s", zonename)) + self:T2(self.lid..string.format("Left Zone %s", zonename)) self.inzones:Remove(zonename, true) end @@ -3174,7 +3218,8 @@ function FLIGHTGROUP:onafterTaskExecute(From, Event, To, Task) local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) -- Set task for group. - self:PushTask(TaskFinal, 1) + --self:PushTask(TaskFinal, 1) + self:SetTask(TaskFinal, 1) end @@ -3267,7 +3312,7 @@ function FLIGHTGROUP:onafterTaskCancel(From, Event, To, Task) local text=string.format("WARNING: No (current) task to cancel!") MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) - self:I(self.lid..text) + self:E(self.lid..text) end @@ -3312,10 +3357,15 @@ function FLIGHTGROUP:onafterTaskDone(From, Event, To, Task) -- Check if this task was the task of the current mission ==> Mission Done! local Mission=self:GetMissionByTaskID(Task.id) + local status=Mission:GetFlightStatus(self) if Mission then - env.info("FF Task Done ==> Mission Done!") - self:MissionDone(Mission) + if status~=AUFTRAG.FlightStatus.PAUSED then + env.info("FF Task Done ==> Mission Done!") + self:MissionDone(Mission) + else + --Mission paused. Do nothing! + end else env.info("FF Task Done but NO mission found ==> _CheckFlightDone in 1 sec") self:_CheckFlightDone(1) @@ -3339,7 +3389,8 @@ end -- @param Ops.Auftrag#AUFTRAG Mission The mission table. function FLIGHTGROUP:onbeforeMissionStart(From, Event, To, Mission) - 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()))) + -- Debug info. + 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 @@ -3372,8 +3423,8 @@ end function FLIGHTGROUP:onafterMissionStart(From, Event, To, Mission) local text=string.format("Starting Mission %s", tostring(Mission.name)) - self:I(self.lid..text) - MESSAGE:New(text, 120, "DEBUG"):ToAllIf(true) + self:T(self.lid..text) + MESSAGE:New(text, 120, self.lid):ToAllIf(true) -- Set current mission. self.currentmission=Mission.auftragsnummer @@ -3398,8 +3449,8 @@ end function FLIGHTGROUP:onafterMissionExecute(From, Event, To, Mission) local text=string.format("Executing Mission %s", tostring(Mission.name)) - self:I(self.lid..text) - MESSAGE:New(text, 120, "DEBUG"):ToAllIf(true) + self:T(self.lid..text) + MESSAGE:New(text, 120, self.lid):ToAllIf(true) -- Set flight mission status to EXECUTING. Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.EXECUTING) @@ -3409,6 +3460,49 @@ function FLIGHTGROUP:onafterMissionExecute(From, Event, To, Mission) end +--- On after "PauseMission" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterPauseMission(From, Event, To) + + local Mission=self:GetMissionCurrent() + + if Mission then + + -- Set flight mission status to EXECUTING. + Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.PAUSED) + + -- Get mission waypoint task. + local Task=Mission:GetFlightWaypointTask(self) + + env.info(string.format("FF pausing current mission %s. Task=%s", tostring(Mission.name), tostring(Task and Task.description or "WTF"))) + + -- Cancelling the mission is actually cancelling the current task. + self:TaskCancel(Task) + + -- Set mission to pause so we can unpause it later. + self.missionpaused=Mission + + end + +end + +--- On after "UnpauseMission" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function FLIGHTGROUP:onafterUnpauseMission(From, Event, To) + + self:MissionStart(self.missionpaused) + + self.missionpaused=nil + +end + + --- On after "MissionCancel" event. Cancels the mission. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -3456,7 +3550,10 @@ end -- @param Ops.Auftrag#AUFTRAG Mission function FLIGHTGROUP:onafterMissionDone(From, Event, To, Mission) - self:I(self.lid..string.format("Mission %s DONE!", Mission.name)) + -- Debug info. + local text=string.format("Mission %s DONE!", Mission.name) + self:I(self.lid..text) + MESSAGE:New(text, 120, self.lid):ToAllIf(true) -- Set Flight status. Mission:SetFlightStatus(self, AUFTRAG.FlightStatus.DONE) @@ -3491,7 +3588,7 @@ function FLIGHTGROUP:RouteToMission(mission, delay) -- Set altitude of mission waypoint. if mission.missionAltitude then waypointcoord.y=mission.missionAltitude - env.info("FF mission altitude [m]="..waypointcoord.y) + env.info(string.format("FF mission altitude %d m = %d ft", waypointcoord.y, UTILS.MetersToFeet(waypointcoord.y))) end -- Add enroute tasks. @@ -3513,10 +3610,15 @@ function FLIGHTGROUP:RouteToMission(mission, delay) -- TODO: better marker text, mission.maker --mission.marker=waypointcoord:MarkToCoalition(mission.name, self:GetCoalition(), true) - mission.marker=waypointcoord:MarkToAll(mission.name, true) + mission.marker=waypointcoord:MarkToAll(string.format("%s %s %s", mission.name, mission.type, self.groupname), true) - --self:SetROE(mission.optionROE) + if mission.optionROE then + self:SetOptionROE(mission.optionROE) + end + if mission.optionROT then + self:SetOptionROT(mission.optionROT) + end end end @@ -3609,23 +3711,6 @@ function FLIGHTGROUP._DestinationOverhead(group, flightgroup, destination) end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Option functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - ---- Set ROE. --- @param #FLIGHTGROUP self --- @param #number roe ROE value. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetOptionROE(roe) - - if self:IsAlive() then - self.group:SetOption(AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_HOLD) - end - -end - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3740,7 +3825,7 @@ function FLIGHTGROUP:AddElementByName(unitname) local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f, category=%d, categoryname=%s, callsign=%s, ai=%s", element.name, element.status, element.skill, element.modex, element.fuelmass, 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) @@ -4204,7 +4289,7 @@ function FLIGHTGROUP:InitWaypoints(waypoints) 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 @@ -4251,7 +4336,7 @@ function FLIGHTGROUP:AddWaypointAir(coordinate, wpnumber, speed, updateroute) table.insert(self.waypoints, wpnumber, wp) -- Debug info. - self:I(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)) -- Shift all waypoint tasks after the inserted waypoint. for _,_task in pairs(self.taskqueue) do @@ -5201,7 +5286,7 @@ function FLIGHTGROUP:GetParking(airbase) -- Safe parking using TO_AC from DCS result. if verysafe and parkingspot.TOAC then free=false - self:I(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).", parkingspot.TerminalID)) + self:T2(self.lid..string.format("Parking spot %d is occupied by other aircraft taking off (TOAC).", parkingspot.TerminalID)) end -- Loop over all obstacles. @@ -5235,7 +5320,7 @@ function FLIGHTGROUP:GetParking(airbase) -- Add parkingspot for this element. table.insert(parking, parkingspot) - self:I(self.lid..string.format("Parking spot %d is free for element %s!", parkingspot.TerminalID, element.name)) + self:T2(self.lid..string.format("Parking spot %d is free for element %s!", parkingspot.TerminalID, element.name)) -- Add the unit as obstacle so that this spot will not be available for the next unit. table.insert(obstacles, {coord=parkingspot.Coordinate, size=element.size, name=element.name, type="element"}) @@ -5246,7 +5331,7 @@ function FLIGHTGROUP:GetParking(airbase) else -- Debug output for occupied spots. - self:I(self.lid..string.format("Parking spot %d is occupied or not big enough!", parkingspot.TerminalID)) + self:T2(self.lid..string.format("Parking spot %d is occupied or not big enough!", parkingspot.TerminalID)) --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) @@ -5260,7 +5345,7 @@ function FLIGHTGROUP:GetParking(airbase) -- No parking spot for at least one asset :( if not gotit then - self:I(self.lid..string.format("WARNING: No free parking spot for element %s", element.name)) + self:E(self.lid..string.format("WARNING: No free parking spot for element %s", element.name)) return nil end @@ -5371,32 +5456,54 @@ end -- OPTION FUNCTIONS ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set ROE for a group. +--- Set the default ROE for the group. This is the ROE state gets when the group is spawned or to which it defaults back after a mission. -- @param #FLIGHTGROUP self --- @param #string roe ROE of group. Default is FLIGHTGROUP.ROE.HOLDFIRE. -function FLIGHTGROUP:SetOptionROE(roe) - if roe==FLIGHTGROUP.ROE.RETURNFIRE then - self.group:OptionROEReturnFire() - elseif roe==FLIGHTGROUP.ROE.WEAPONFREE then - self.group:OptionROEWeaponFree() - else - self.group:OptionROEHoldFire() - end - self.ROEcurrent=roe +-- @param #number roe ROE of group. Default is ENUMS.ROE.ReturnFire. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetDefaultROE(roe) + self.optionROEdefault=roe or ENUMS.ROE.ReturnFire + return self end - ---- Set ROT for a group. +--- Set current ROE for the group. -- @param #FLIGHTGROUP self --- @param #string rot ROT of group. -function FLIGHTGROUP:SetOptionROT(rot) - if rot==FLIGHTGROUP.ROT.PASSIVE then - self.group:OptionROTPassiveDefense() - elseif self.rot==FLIGHTGROUP.ROT.NOREACT then - self.group:OptionROTEvadeFire() +-- @param #string roe ROE of group. Default is the value defined by :SetDefaultROE(). +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionROE(roe) + roe=roe or self.optionROEdefault + if self:IsAlive() then + self.group:OptionROE(roe) + self.optionROEcurrent=roe + self:I(self.lid..string.format("Setting current ROE=%d (0=WeaponFree, 1=OpenFireWeaponFree, 2=OpenFire, 3=ReturnFire, 4=WeaponHold)", self.optionROEcurrent)) else - self.group:OptionROTNoReaction() + -- WARNING end + return self +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 #FLIGHTGROUP self +-- @param #number roe ROE of group. Default is ENUMS.ROT.PassiveDefense. +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetDefaultROT(roe) + self.optionROTdefault=roe or ENUMS.ROT.PassiveDefense + return self +end + +--- Set ROT for the group. +-- @param #FLIGHTGROUP self +-- @param #string rot ROT of group. Default is the value defined by :SetDefaultROT(). +-- @return #FLIGHTGROUP self +function FLIGHTGROUP:SetOptionROT(rot) + rot=rot or self.optionROTdefault + if self:IsAlive() then + self.group:OptionROT(rot) + self.optionROTcurrent=rot + self:I(self.lid..string.format("Setting current ROT=%d (0=NoReaction, 1=Passive, 2=Evade, 3=ByPass, 4=AllowAbort)", self.optionROTcurrent)) + else + -- WARNING + end + return self end diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 630c95e52..c3d94face 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -208,6 +208,32 @@ function INTEL:SetForgetTime(TimeInterval) 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 @@ -219,14 +245,15 @@ end -- @param #INTEL self -- @param #table Categories Filter categories, e.g. {Group.Category.AIRPLANE, Group.Category.HELICOPTER}. -- @return #INTEL self -function INTEL:SetFilterCategory(Categories) - if type(Categories)~="table" then - Categories={Categories} +function INTEL:FilterCategoryGroup(GroupCategories) + if type(GroupCategories)~="table" then + GroupCategories={GroupCategories} end - self.filterCategory=Categories + + self.filterCategoryGroup=GroupCategories - local text="Filter categories: " - for _,category in pairs(self.filterCategory) do + local text="Filter group categories: " + for _,category in pairs(self.filterCategoryGroup) do text=text..string.format("%d,", category) end self:I(self.lid..text) diff --git a/Moose Development/Moose/Ops/Squadron.lua b/Moose Development/Moose/Ops/Squadron.lua index 87e36fbb2..2943474af 100644 --- a/Moose Development/Moose/Ops/Squadron.lua +++ b/Moose Development/Moose/Ops/Squadron.lua @@ -126,7 +126,10 @@ function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") -- Start FSM. - self:AddTransition("*", "Status", "*") -- SQUADRON status update + self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("Running", "Pause", "Paused") -- Pause squadron. + self:AddTransition("Paused", "Unpause", "Running") -- Unpause squadron. + self:AddTransition("*", "Stop", "Stopped") -- Stop squadron. ------------------------ @@ -315,7 +318,7 @@ end -- @param #SQUADRON self function SQUADRON:_CheckAssetStatus() - for _,_assets in pairs(self.assets) do + for _,_asset in pairs(self.assets) do local asset=_asset --#SQUADRON.Flight flight.flightgroup:IsSpawned() @@ -347,7 +350,7 @@ function SQUADRON:CanMission(Mission) if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then -- Correct refueling system. else - env.info(string.format("wrong refuling system mi=%s ta=%s", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) + self:I(self.lid..string.format("wrong refuling system mi=%s ta=%s", tostring(Mission.refuelSystem), tostring(self.tankerSystem))) cando=false end diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index a7cb3b9b9..2229b9043 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -7,9 +7,6 @@ --- [DCS Enum world](https://wiki.hoggitworld.com/view/DCS_enum_world) -- @type ENUMS --- @field #ENUMS.ROE Rules Of Engagement. --- @field #ENUMS.ROT Reation On Threat. --- @field #ENUMS.WeaponFlag Weapon flags. --- The world singleton contains functions centered around two different but extremely useful functions. -- * Events and event handlers are all governed within world. @@ -22,22 +19,24 @@ ENUMS = {} --- Rules of Engagement. -- @type ENUMS.ROE ENUMS.ROE = { - HoldFire = 1, - ReturnFire = 2, - OpenFire = 3, - WeaponFree = 4 + WeaponFree=0, + OpenFireWeaponFree=1, + OpenFire=2, + ReturnFire=3, + WeaponHold=4, } ---- Reaction On Thrat. +--- Reaction On Threat. -- @type ENUMS.ROT ENUMS.ROT = { - NoReaction = 1, - PassiveDefense = 2, - EvadeFire = 3, - Vertical = 4 + NoReaction=0, + PassiveDefense=1, + EvadeFire=2, + ByPassAndEscape=3, + AllowAbortMission=4, } ---- Weapon types +--- Weapon types. -- @type ENUMS.WeaponFlag ENUMS.WeaponFlag={ Auto=3221225470, --AnyWeapon (AnyBomb + AnyRocket + AnyMissile + Cannons) @@ -67,6 +66,8 @@ ENUMS.WeaponFlag={ AnyRocket=30720 -- (LightRocket + MarkerRocket + CandleRocket + HeavyRocket) } +--- Mission tasks. +-- @type ENUMS.MissionTask ENUMS.MissionTask={ NOTHING="Nothing", AFAC="AFAC", diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index b161553c0..d4633fcc9 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -822,15 +822,16 @@ end --- (AIR) Attack a Controllable. -- @param #CONTROLLABLE self --- @param Wrapper.Controllable#CONTROLLABLE AttackGroup The Controllable to be attacked. +-- @param Wrapper.Group#GROUP AttackGroup The Group to be attacked. -- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage. -- @param DCS#AI.Task.WeaponExpend WeaponExpend (optional) Determines how much weapon will be released at each attack. If parameter is not defined the unit / controllable will choose expend on its own discretion. -- @param #number AttackQty (optional) This parameter limits maximal quantity of attack. The aicraft/controllable will not make more attack than allowed even if the target controllable not destroyed and the aicraft/controllable still have ammo. If not defined the aircraft/controllable will attack target until it will be destroyed or until the aircraft/controllable will run out of ammo. -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param DCS#Distance Altitude (optional) Desired attack start altitude. Controllable/aircraft will make its attacks from the altitude. If the altitude is too low or too high to use weapon aircraft/controllable will choose closest altitude to the desired attack start altitude. If the desired altitude is defined controllable/aircraft will not attack from safe altitude. -- @param #boolean AttackQtyLimit (optional) The flag determines how to interpret attackQty parameter. If the flag is true then attackQty is a limit on maximal attack quantity for "AttackGroup" and "AttackUnit" tasks. If the flag is false then attackQty is a desired attack quantity for "Bombing" and "BombingRunway" tasks. +-- @param #boolean GroupAttack (Optional) If true, attack as group. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit ) +function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack ) --self:F2( { self.ControllableName, AttackGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit } ) -- AttackGroup = { @@ -857,9 +858,10 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At attackQtyLimit = AttackQty and true or false, attackQty = AttackQty, directionEnabled = Direction and true or false, - direction = Direction and math.rad(Direction) or nil, + direction = Direction and math.rad(Direction) or 0, altitudeEnabled = Altitude and true or false, altitude = Altitude, + groupAttack = GroupAttack and true or false, }, } @@ -885,7 +887,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta groupAttack = GroupAttack and GroupAttack or false, expend = WeaponExpend or "Auto", directionEnabled = Direction and true or false, - direction = Direction and math.rad(Direction) or nil, + direction = Direction and math.rad(Direction) or 0, altitudeEnabled = Altitude and true or false, altitude = Altitude, attackQtyLimit = AttackQty and true or false, @@ -956,7 +958,7 @@ function CONTROLLABLE:TaskAttackMapObject( Vec2, GroupAttack, WeaponExpend, Atta attackQtyLimit = AttackQty and true or false, attackQty = AttackQty, directionEnabled = Direction and true or false, - direction = Direction, + direction = Direction and math.rad(Direction) or 0, altitudeEnabled = Altitude and true or false, altitude = Altitude, weaponType = WeaponType or 1073741822, @@ -996,7 +998,7 @@ function CONTROLLABLE:TaskCarpetBombing(Vec2, GroupAttack, WeaponExpend, AttackQ attackQtyLimit = AttackQty and true or false, attackQty = AttackQty, directionEnabled = Direction and true or false, - direction = Direction and math.rad(Direction) or nil, + direction = Direction and math.rad(Direction) or 0, altitudeEnabled = Altitude and true or false, altitude = Altitude, } @@ -2956,6 +2958,32 @@ function CONTROLLABLE:SetOption(OptionID, OptionValue) return nil end +--- Set option for Rules of Engagement (ROE). +-- @param Wrapper.Controllable#CONTROLLABLE self +-- @param #number ROEvalue ROE value. See ENUMS.ROE. +-- @return Wrapper.Controllable#CONTROLLABLE self +function CONTROLLABLE:OptionROE(ROEvalue) + + local DCSControllable = self:GetDCSObject() + + if DCSControllable then + + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption(AI.Option.Air.id.ROE, ROEvalue ) + elseif self:IsGround() then + Controller:setOption(AI.Option.Ground.id.ROE, ROEvalue ) + elseif self:IsShip() then + Controller:setOption(AI.Option.Naval.id.ROE, ROEvalue ) + end + + return self + end + + return nil +end + --- Can the CONTROLLABLE hold their weapons? -- @param #CONTROLLABLE self -- @return #boolean @@ -3198,6 +3226,27 @@ function CONTROLLABLE:OptionROTNoReaction() return nil end +--- Set Reation On Threat behaviour. +-- @param #CONTROLLABLE self +-- @param #number ROTvalue ROT value. See ENUMS.ROT. +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROT(ROTvalue) + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, ROTvalue ) + end + + return self + end + + return nil +end + --- Can the CONTROLLABLE evade using passive defenses? -- @param #CONTROLLABLE self -- @return #boolean