diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index e8a659885..d6acf2797 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -68,7 +68,7 @@ ARMYGROUP = { --- Army Group version. -- @field #string version -ARMYGROUP.version="0.7.9" +ARMYGROUP.version="0.8.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -775,8 +775,8 @@ function ARMYGROUP:Status() end -- Info text. - local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f", - fsmstate, nelem, Nelem, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, life, speed, speedEx, hdg, ammo, ndetected, cargo) + local text=string.format("%s [%d/%d]: ROE/AS=%d/%d | T/M=%d/%d | Wp=%d[%d]-->%d[%d]/%d [%s] | Life=%.1f | v=%.1f (%d) [%s] | Hdg=%03d | Ammo=%d | Detect=%s | Cargo=%.1f", + fsmstate, nelem, Nelem, roe, als, nTaskTot, nMissions, wpidxCurr, wpuidCurr, wpidxNext, wpuidNext, wpN, wpF, life, speed, speedEx, formation, hdg, ammo, ndetected, cargo) self:I(self.lid..text) end @@ -1071,89 +1071,182 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) -- Next waypoint. local wp=self.waypoints[n] --Ops.OpsGroup#OPSGROUP.Waypoint + -- Current position. + local coordinate=self:GetCoordinate() + + -- Road coordinate. + local coordRoad=coordinate:GetClosestPointToRoad() + + -- Road distance. + local roaddist=coordinate:Get2DDistance(coordRoad) + -- Formation at the current position. local formation0=wp.action if formation0==ENUMS.Formation.Vehicle.OnRoad then - if wp.roadcoord then - if wp.roaddist>10 then - formation0=ENUMS.Formation.Vehicle.OffRoad - end - else + -- Next waypoint is on road. Check if we are already on road. + if roaddist>10 then + -- Currently off road ==> we add an on road WP later. formation0=ENUMS.Formation.Vehicle.OffRoad + else + -- Already on road. We won't add an extra on road WP. + formation0=ENUMS.Formation.Vehicle.OnRoad end end + + -- Debug + --env.info(self.lid.."FF formation0="..tostring(formation0)) -- Current point. - local current=self:GetCoordinate():WaypointGround(UTILS.MpsToKmph(self.speedWp), formation0) --ENUMS.Formation.Vehicle.OffRoad) + local current=coordinate:WaypointGround(UTILS.MpsToKmph(self.speedWp), formation0) table.insert(waypoints, 1, current) - -- Loop over waypoints. - for j=n, N do + -- Check if route consists of more than one waypoint (otherwise we have no previous waypoint) + if N-n>0 then - -- Index of previous waypoint. - local i=j-1 + -- Loop over waypoints. + for j=n, N do - -- If we go to the first waypoint j=1 ==> i=0, so we take the last waypoint passed. E.g. when adinfinitum and passed final waypoint. - if i==0 then - i=self.currentwp - end - - -- Next waypoint. - local wp=UTILS.DeepCopy(self.waypoints[j]) --Ops.OpsGroup#OPSGROUP.Waypoint - - -- Previous waypoint. Index is i and not i-1 because we added the current position. - local wp0=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint - - --local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s", i, wp.action, wp0.action) - --env.info(text) - - -- Speed. - if Speed then - wp.speed=UTILS.KnotsToMps(tonumber(Speed)) - else - -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. - if wp.speed<0.1 then - wp.speed=UTILS.KmphToMps(self.speedCruise) + -- Index of previous waypoint. + local i=j-1 + + -- If we go to the first waypoint j=1 ==> i=0, so we take the last waypoint passed. E.g. when adinfinitum and passed final waypoint. + if i==0 then + i=self.currentwp end - end - -- Formation. - if self.formationPerma then - wp.action=self.formationPerma - elseif Formation then - wp.action=Formation - end - - -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". - if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then - - --env.info("FF adding waypoint0 on road #"..i) + -- Next waypoint. We create a copy because we need to modify it. + local wp=UTILS.DeepCopy(self.waypoints[j]) --Ops.OpsGroup#OPSGROUP.Waypoint - -- Add "On Road" waypoint in between. - local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - - -- Insert road waypoint. - table.insert(waypoints, wproad) - end - - -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". - if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then - - --env.info("FF adding waypoint on road #"..i) - - -- The real waypoint is actually off road. - wp.action=ENUMS.Formation.Vehicle.OffRoad + -- Previous waypoint. Index is i and not i-1 because we added the current position. + local wp0=self.waypoints[i] --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Debug + if false and self.attribute==GROUP.Attribute.GROUND_APC then + local text=string.format("FF Update: i=%d, wp[i]=%s, wp[i-1]=%s", i, wp.action, wp0.action) + env.info(text) + end - -- Add "On Road" waypoint in between. - local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint - - -- Insert road waypoint. - table.insert(waypoints, wproad) - end - + -- Speed. + if Speed then + wp.speed=UTILS.KnotsToMps(tonumber(Speed)) + else + -- Take default waypoint speed. But make sure speed>0 if patrol ad infinitum. + if wp.speed<0.1 then + wp.speed=UTILS.KmphToMps(self.speedCruise) + end + end + + -- Formation. + if self.formationPerma then + wp.action=self.formationPerma + elseif Formation then + wp.action=Formation + end + + -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". + if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp0.roaddist>=0 then - -- Add waypoint. - table.insert(waypoints, wp) + -- Add "On Road" waypoint in between. + local wproad=wp0.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Debug + --wp0.roadcoord:MarkToAll(self.lid.." Added road wp near "..tostring(wproad.action)) + + -- Insert road waypoint. + table.insert(waypoints, wproad) + end + + -- Add waypoint in between because this waypoint is "On Road" but lies "Off Road". + if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>=0 then + + -- The real waypoint is actually off road. + wp.action=ENUMS.Formation.Vehicle.OffRoad + + -- Add "On Road" waypoint in between. + local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Debug + --wp.roadcoord:MarkToAll(self.lid.." Added road wp far "..tostring(wproad.action)) + + -- Insert road waypoint. + table.insert(waypoints, wproad) + end + + -- Debug + --wp.coordinate:MarkToAll(self.lid.." Added wp actual"..tostring(wp.action)) + + -- Add waypoint. + table.insert(waypoints, wp) + end + + else + + --- + -- This is the case, where we have only one WP left. + -- Could be because we had only one WP and did a detour (temp waypoint, which was deleted). + --- + + -- Next waypoint. + local wp=UTILS.DeepCopy(self.waypoints[n]) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Speed. + if wp.speed<0.1 then + wp.speed=UTILS.KmphToMps(self.speedCruise) + end + + -- Formation. + local formation=wp.action + if self.formationPerma then + formation=self.formationPerma + elseif Formation then + formation=Formation + end + + -- Debug + --env.info(self.lid..string.format("FF Formation %s", formation)) + + -- Add road waypoint. + if formation==ENUMS.Formation.Vehicle.OnRoad then + + if roaddist>10 then + + -- Add "On Road" waypoint in between. + local wproad=coordRoad:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Debug + --coordRoad:MarkToAll(self.lid.." Added road wp near "..tostring(wp.action)) + + -- Insert road waypoint. + table.insert(waypoints, wproad) + + end + + if wp.roaddist>10 then + + -- Add "On Road" waypoint in between. + local wproad=wp.roadcoord:WaypointGround(UTILS.MpsToKmph(wp.speed), ENUMS.Formation.Vehicle.OnRoad) --Ops.OpsGroup#OPSGROUP.Waypoint + + -- Debug + --wp.roadcoord:MarkToAll(self.lid.." Added road wp far "..tostring(wp.action)) + + -- Insert road waypoint. + table.insert(waypoints, wproad) + + end + + end + + -- Waypoint set set to on-road but lies off-road. We set it to off-road. the on-road wp has been inserted. + if wp.action==ENUMS.Formation.Vehicle.OnRoad and wp.roaddist>10 then + wp.action=ENUMS.Formation.Vehicle.OffRoad + end + + -- Debug + --wp.coordinate:MarkToAll(self.lid.." Added coord "..tostring(wp.action)) + + -- Add actual waypoint. + table.insert(waypoints, wp) + end -- First (next wp). @@ -1166,7 +1259,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, N, Speed, Formation) self.speedWp=wp.speed -- Debug output. - if self.verbose>=10 then + if self.verbose>=10 then --or self.attribute==GROUP.Attribute.GROUND_APC then for i,_wp in pairs(waypoints) do local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint @@ -1662,7 +1755,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Formation self:SwitchROE(ENUMS.ROE.OpenFire) -- ID of current waypoint. - local uid=self:GetWaypointCurrent().uid + local uid=self:GetWaypointCurrentUID() -- Set formation. self.engage.Formation=Formation or ENUMS.Formation.Vehicle.Vee @@ -1844,7 +1937,7 @@ end function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) -- Debug info. - self:T(self.lid..string.format("AddWaypoint Formation = %s",tostring(Formation) or "none")) + self:T(self.lid..string.format("AddWaypoint Formation = %s", tostring(Formation))) -- Create coordinate. local coordinate=self:_CoordinateFromObject(Coordinate) @@ -1862,8 +1955,10 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation elseif self.option.Formation then Formation = self.option.Formation else - Formation = "On Road" + -- Default formation is on road. + Formation = ENUMS.Formation.Vehicle.OnRoad end + self:T2(self.lid..string.format("Formation set to = %s", tostring(Formation))) end -- Create a Ground waypoint. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index fa10e8434..e49dc25c4 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -59,6 +59,7 @@ -- @field #number Nelements Number of elements (units) assigned to mission. -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. -- @field #number Tover Mission abs. time stamp, when mission was over. +-- @field #boolean updateDCSTask If `true`, DCS task is updated at every status update of the assigned groups. -- @field #table conditionStart Condition(s) that have to be true, before the mission will be started. -- @field #table conditionSuccess If all conditions are true, the mission is cancelled. -- @field #table conditionFailure If all conditions are true, the mission is cancelled. @@ -68,9 +69,12 @@ -- @field #number orbitAltitude Orbit altitude in meters. -- @field #number orbitHeading Orbit heading in degrees. -- @field #number orbitLeg Length of orbit leg in meters. --- @field Core.Point#COORDINATE orbitRaceTrack Race-track orbit coordinate. +-- @field DCS#Vec2 orbitOffsetVec2 2D offset vector. +-- @field DCS#Vec2 orbitVec2 2D orbit vector. +-- @field #number orbitDeltaR Distance threshold in meters for moving orbit targets. -- -- @field Ops.Target#TARGET engageTarget Target data to engage. +-- @field #number targetHeading Heading of target in degrees. -- -- @field Ops.Operation#OPERATION operation Operation this mission is part of. -- @@ -624,7 +628,7 @@ AUFTRAG.Category={ --- AUFTRAG class version. -- @field #string version -AUFTRAG.version="0.9.6" +AUFTRAG.version="0.9.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -633,7 +637,8 @@ AUFTRAG.version="0.9.6" -- TODO: Replace engageRange by missionRange. Here and in other classes. CTRL+H is your friend! -- TODO: Mission success options damaged, destroyed. -- TODO: F10 marker to create new missions. --- TODO: Add recovery tanker mission for boat ops. +-- DONE: Add orbit mission for moving anker points. +-- DONE: Add recovery tanker mission for boat ops. -- DONE: Added auftrag category. -- DONE: Missions can be assigned to multiple legions. -- DONE: Option to assign a specific payload for the mission (requires an AIRWING). @@ -1020,7 +1025,7 @@ end --- **[AIR]** Create an ORBIT mission, which can be either a circular orbit or a race-track pattern. -- @param #AUFTRAG self -- @param Core.Point#COORDINATE Coordinate Where to orbit. --- @param #number Altitude Orbit altitude in feet. Default is y component of `Coordinate`. +-- @param #number Altitude Orbit altitude in feet above sea level. Default is y component of `Coordinate`. -- @param #number Speed Orbit speed in knots. Default 350 KIAS. -- @param #number Heading Heading of race-track pattern in degrees. If not specified, a circular orbit is performed. -- @param #number Leg Length of race-track in NM. If not specified, a circular orbit is performed. @@ -1028,26 +1033,35 @@ end function AUFTRAG:NewORBIT(Coordinate, Altitude, Speed, Heading, Leg) local mission=AUFTRAG:New(AUFTRAG.Type.ORBIT) - - -- Altitude. + + -- Target. + mission:_TargetFromObject(Coordinate) + + -- Set Altitude. if Altitude then mission.orbitAltitude=UTILS.FeetToMeters(Altitude) else mission.orbitAltitude=Coordinate.y end - Coordinate.y=mission.orbitAltitude - - mission:_TargetFromObject(Coordinate) - - mission.orbitSpeed = UTILS.KnotsToMps(Speed or 350) -- the DCS Task itself will shortly be build with this so MPS + + -- Orbit speed in m/s. + mission.orbitSpeed = UTILS.KnotsToMps(UTILS.KnotsToAltKIAS(Speed or 350, UTILS.MetersToFeet(mission.orbitAltitude))) + + -- Mission speed in km/h. mission.missionSpeed = UTILS.KnotsToKmph(Speed or 350) - - if Heading and Leg then - mission.orbitHeading=Heading + + if Leg then mission.orbitLeg=UTILS.NMToMeters(Leg) - mission.orbitRaceTrack=Coordinate:Translate(mission.orbitLeg, mission.orbitHeading, true) - end + -- Relative heading + if Heading and Heading<0 then + mission.orbitHeadingRel=true + Heading=-Heading + end + + -- Heading if given. + mission.orbitHeading=Heading + end -- Mission options: mission.missionAltitude=mission.orbitAltitude*0.9 @@ -1093,6 +1107,53 @@ function AUFTRAG:NewORBIT_RACETRACK(Coordinate, Altitude, Speed, Heading, Leg) return mission end +--- **[AIR]** Create an ORBIT mission, where the aircraft will fly a circular or race-track pattern over a given group or unit. +-- @param #AUFTRAG self +-- @param Wrapper.Group#GROUP Group Group where to orbit around. Can also be a UNIT object. +-- @param #number Altitude Orbit altitude in feet. Default is 6,000 ft. +-- @param #number Speed Orbit speed in knots. Default 350 KIAS. +-- @param #number Leg Length of race-track in NM. Default nil. +-- @param #number Heading Heading of race-track pattern in degrees. Default is heading of the group. +-- @param DCS#Vec2 OffsetVec2 Offset 2D-vector {x=0, y=0} in NM with respect to the group. Default directly overhead. Can also be given in polar coordinates `{r=5, phi=45}`. +-- @param #number Distance Threshold distance in NM before orbit pattern is updated. Default 5 NM. +-- @return #AUFTRAG self +function AUFTRAG:NewORBIT_GROUP(Group, Altitude, Speed, Leg, Heading, OffsetVec2, Distance) + + -- Set default altitude. + Altitude = Altitude or 6000 + + -- Create orbit mission. + local mission=AUFTRAG:NewORBIT(Group, Altitude, Speed, Heading, Leg) + + -- DCS tasks needs to be updated from time to time. + mission.updateDCSTask=true + + -- Convert offset vector to meters. + if OffsetVec2 then + if OffsetVec2.x then + OffsetVec2.x=UTILS.NMToMeters(OffsetVec2.x) + end + if OffsetVec2.y then + OffsetVec2.y=UTILS.NMToMeters(OffsetVec2.y) + end + if OffsetVec2.r then + OffsetVec2.r=UTILS.NMToMeters(OffsetVec2.r) + end + end + + -- Offset vector. + mission.orbitOffsetVec2=OffsetVec2 + + -- Pattern update distance. + mission.orbitDeltaR=UTILS.NMToMeters(Distance or 5) + + -- Update task with offset etc. + mission:GetDCSMissionTask() + + return mission +end + + --- **[AIR]** Create a Ground Controlled CAP (GCICAP) mission. Flights with this task are considered for A2A INTERCEPT missions by the CHIEF class. They will perform a compat air patrol but not engage by -- themselfs. They wait for the CHIEF to tell them whom to engage. -- @param #AUFTRAG self @@ -1631,24 +1692,44 @@ function AUFTRAG:NewRESCUEHELO(Carrier) return mission end ---- **[AIRPANE]** Create a RECOVERY TANKER mission. **WIP and not working coorectly yet!** +--- **[AIRPANE]** Create a RECOVERY TANKER mission. -- @param #AUFTRAG self -- @param Wrapper.Unit#UNIT Carrier The carrier unit. +-- @param #number Altitude Orbit altitude in feet. Default is 6,000 ft. +-- @param #number Speed Orbit speed in knots. Default 250 KIAS. +-- @param #number Leg Length of race-track in NM. Default 14 NM. +-- @param #number RelHeading Relative heading [0, 360) of race-track pattern in degrees wrt heading of the carrier. Default is heading of the carrier. +-- @param #number OffsetDist Relative distance of the first race-track point wrt to the carrier. Default 6 NM. +-- @param #number OffsetAngle Relative angle of the first race-track point wrt. to the carrier. Default 180 (behind the boat). +-- @param #number UpdateDistance Threshold distance in NM before orbit pattern is updated. Default 5 NM. -- @return #AUFTRAG self -function AUFTRAG:NewRECOVERYTANKER(Carrier) +function AUFTRAG:NewRECOVERYTANKER(Carrier, Altitude, Speed, Leg, RelHeading, OffsetDist, OffsetAngle, UpdateDistance) + + -- Six NM astern. + local OffsetVec2={r=OffsetDist or 6, phi=OffsetAngle or 180} + + -- Default leg. + Leg=Leg or 14 + + -- Default Speed. + Speed=Speed or 250 + + local Heading=nil + if RelHeading then + Heading=-math.abs(RelHeading) + end + + -- Create orbit mission. + local mission=AUFTRAG:NewORBIT_GROUP(Carrier, Altitude, Speed, Leg, Heading, OffsetVec2, UpdateDistance) - local mission=AUFTRAG:New(AUFTRAG.Type.RECOVERYTANKER) - - mission:_TargetFromObject(Carrier) + -- Set the type. + mission.type=AUFTRAG.Type.RECOVERYTANKER -- Mission options: mission.missionTask=ENUMS.MissionTask.REFUELING - mission.missionFraction=0.5 + mission.missionFraction=0.9 mission.optionROE=ENUMS.ROE.WeaponHold mission.optionROT=ENUMS.ROT.NoReaction - - mission.missionAltitude=UTILS.FeetToMeters(6000) - mission.missionSpeed=UTILS.KnotsToKmph(274) mission.categories={AUFTRAG.Category.AIRPLANE} @@ -4974,6 +5055,17 @@ function AUFTRAG:GetTargetCoordinate() return nil end +--- Get heading of target. +-- @param #AUFTRAG self +-- @return #number Heading of target in degrees. +function AUFTRAG:GetTargetHeading() + if self.engageTarget then + local heading=self.engageTarget:GetHeading() + return heading + end + return nil +end + --- Get name of the target. -- @param #AUFTRAG self -- @return #string Name of the target or "N/A". @@ -5239,6 +5331,17 @@ function AUFTRAG:_SetLogID() return self end + +--- Update DCS task. +-- @param #AUFTRAG self +-- @return #AUFTRAG self +function AUFTRAG:_UpdateTask() + + + + return self +end + --- Update mission F10 map marker. -- @param #AUFTRAG self -- @return #AUFTRAG self @@ -5411,48 +5514,7 @@ function AUFTRAG:GetDCSMissionTask() DCStask.params=param table.insert(DCStasks, DCStask) - - elseif self.type==AUFTRAG.Type.RECOVERYTANKER then - - ---------------------------- - -- RECOVERYTANKER Mission -- - ---------------------------- - - -- Get the carrier unit. - local Carrier=self:GetObjective() --Wrapper.Unit#UNIT - - -- Carrier coordinate. - local Coord=Carrier:GetCoordinate() - - -- Get current heading of carrier. - local hdg=Carrier:GetHeading() - - -- Altitude - local Altitude=self.missionAltitude - - -- Race-track distances. - local distStern=UTILS.NMToMeters(4) - local distBow=UTILS.NMToMeters(10) - - -- Racetrack pattern points. - local p1=Coord:Translate(distStern, hdg):SetAltitude(self.missionAltitude) - local p2=Coord:Translate(distBow, hdg):SetAltitude(self.missionAltitude) - - p1:MarkToAll("p1") - p2:MarkToAll("p2") - - -- Set speed in m/s. - local Speed=UTILS.KmphToMps(self.missionSpeed) - - -- Orbit task. - local DCStask=CONTROLLABLE.TaskOrbit(nil, p1, Altitude, Speed, p2) - - -- Set carrier as parameter. - DCStask.params.carrier=Carrier - - -- Add to DCS tasks. - table.insert(DCStasks, DCStask) - + elseif self.type==AUFTRAG.Type.INTERCEPT then ----------------------- @@ -5522,7 +5584,7 @@ function AUFTRAG:GetDCSMissionTask() table.insert(DCStasks, DCStask) - elseif self.type==AUFTRAG.Type.TANKER then + elseif self.type==AUFTRAG.Type.TANKER or self.type==AUFTRAG.Type.RECOVERYTANKER then -------------------- -- TANKER Mission -- @@ -5890,17 +5952,91 @@ function AUFTRAG:GetDCSMissionTask() self.type==AUFTRAG.Type.CAS or self.type==AUFTRAG.Type.GCICAP or self.type==AUFTRAG.Type.AWACS or - self.type==AUFTRAG.Type.TANKER then + self.type==AUFTRAG.Type.TANKER or + self.type==AUFTRAG.Type.RECOVERYTANKER then ------------------- -- ORBIT Mission -- ------------------- - local Coordinate=self:GetTargetCoordinate() + -- Get/update orbit vector. + self.orbitVec2=self:GetTargetVec2() + + if self.orbitVec2 then + + -- Heading of the target. + self.targetHeading=self:GetTargetHeading() - local DCStask=CONTROLLABLE.TaskOrbit(nil, Coordinate, self.orbitAltitude, self.orbitSpeed, self.orbitRaceTrack) - - table.insert(DCStasks, DCStask) + local OffsetVec2=nil --DCS#Vec2 + if (self.orbitOffsetVec2~=nil) then + OffsetVec2=UTILS.DeepCopy(self.orbitOffsetVec2) + end + + if OffsetVec2 then + + if self.orbitOffsetVec2.r then + -- Polar coordinates + local r=self.orbitOffsetVec2.r + local phi=(self.orbitOffsetVec2.phi or 0) + self.targetHeading + + OffsetVec2.x=r*math.cos(math.rad(phi)) + OffsetVec2.y=r*math.sin(math.rad(phi)) + else + -- Cartesian coordinates + OffsetVec2.x=self.orbitOffsetVec2.x + OffsetVec2.y=self.orbitOffsetVec2.y + end + + end + + -- Actual orbit position with possible offset. + local orbitVec2=OffsetVec2 and UTILS.Vec2Add(self.orbitVec2, OffsetVec2) or self.orbitVec2 + + -- Check for race-track pattern. + local orbitRaceTrack=nil --DCS#Vec2 + if self.orbitLeg then + + -- Default heading is due North. + local heading=0 + + -- Check if specific heading was specified. + if self.orbitHeading then + + -- Is heading realtive to target? + if self.orbitHeadingRel then + -- Relative heading wrt target. + heading=self.targetHeading+self.orbitHeading + else + -- Take given heading. + heading=self.orbitHeading + end + + else + -- Not specific heading specified ==> Take heading of target. + heading=self.targetHeading or 0 + end + + -- Race-track vector. + orbitRaceTrack=UTILS.Vec2Translate(orbitVec2, self.orbitLeg, heading) + end + + -- Debug + --UTILS.RemoveMark(self.orbitCenterMarkID) + --self.orbitCenterMarkID=COORDINATE:NewFromVec2(orbitVec2):MarkToAll("Orbit Center") + + -- Debug show arrow. + --if orbitRaceTrack then + --UTILS.RemoveMark(self.orbitArrowMarkID) + --self.orbitArrowMarkID=COORDINATE:NewFromVec2(orbitVec2):ArrowToAll(COORDINATE:NewFromVec2(orbitRaceTrack)) + --end + + -- Create orbit task. + local DCStask=CONTROLLABLE.TaskOrbit(nil, orbitVec2, self.orbitAltitude, self.orbitSpeed, orbitRaceTrack) + + -- Add DCS task. + table.insert(DCStasks, DCStask) + + end end @@ -6087,6 +6223,8 @@ function AUFTRAG.CheckMissionCapabilityAll(MissionTypes, Capabilities) return res end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 5433a00a4..b83e3c352 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -42,7 +42,8 @@ -- -- # The CHIEF Concept -- --- The Chief of staff gathers INTEL and assigns missions (AUFTRAG) the airforce, army and/or navy. +-- The Chief of staff gathers INTEL and assigns missions (AUFTRAG) to the airforce, army and/or navy. The distinguished feature here is that this class combines all three +-- forces under one hood. Therefore, this class be used as an air-to-air, air-to-ground, ground-to-ground, air-to-sea, sea-to-ground, etc. dispachter. -- -- # Territory -- @@ -299,10 +300,14 @@ CHIEF.Strategy = { -- @field Ops.OpsZone#OPSZONE opszone OPS zone. -- @field #number prio Priority. -- @field #number importance Importance. --- @field #table resourceEmpty Resource list. --- @field #table resourceOccup Resource list. +-- @field #CHIEF.Resources resourceEmpty List of resources employed when the zone is empty. +-- @field #CHIEF.Resources resourceOccup List of resources employed when the zone is occupied by an enemy. -- @field #table missions Mission. +--- Resource list. +-- @type CHIEF.Resources +-- @field <#CHIEF.Resource> List of resources. + --- Resource. -- @type CHIEF.Resource -- @field #string MissionType Mission type, e.g. `AUFTRAG.Type.BAI`. @@ -311,16 +316,26 @@ CHIEF.Strategy = { -- @field #table Attributes Generalized attribute, e.g. `{GROUP.Attribute.GROUND_INFANTRY}`. -- @field #table Properties Properties ([DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)), e.g. `"Attack helicopters"` or `"Mobile AAA"`. -- @field Ops.Auftrag#AUFTRAG mission Attached mission. +-- @field #number carrierNmin Min number of assets. +-- @field #number carrierNmax Max number of assets. +-- @field #table cargoCategories Group categories. +-- @field #table cargoAttributes Generalized attribute, e.g. `{GROUP.Attribute.GROUND_INFANTRY}`. +-- @field #table cargoProperties Properties ([DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)), e.g. `"Attack helicopters"` or `"Mobile AAA"`. +-- @field #table carrierCategories Group categories. +-- @field #table carrierAttributes Generalized attribute, e.g. `{GROUP.Attribute.GROUND_INFANTRY}`. +-- @field #table carrierProperties Properties ([DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)), e.g. `"Attack helicopters"` or `"Mobile AAA"`. + --- CHIEF class version. -- @field #string version -CHIEF.version="0.4.0" +CHIEF.version="0.5.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Let user specify amount of resources. +-- TODO: PLAYERTASK integration. +-- DONE: Let user specify amount of resources. -- DONE: Tactical overview. -- DONE: Add event for opsgroups on mission. -- DONE: Add event for zone captured. @@ -736,44 +751,45 @@ end -- @param #number Nmax Max number of requried assets. Default 1. -- @param #table Attributes Generalized attribute(s). Default `nil`. -- @param #table Properties DCS attribute(s). Default `nil`. --- @return #table The resource object. +-- @return #CHIEF.Resources The newly created resource list table. +-- @return #CHIEF.Resource The resource object that was added. function CHIEF:CreateResource(MissionType, Nmin, Nmax, Attributes, Properties) - local resource={} + local resources={} - self:AddToResource(resource, MissionType, Nmin, Nmax, Attributes, Properties) + local resource=self:AddToResource(resources, MissionType, Nmin, Nmax, Attributes, Properties) - return resource + return resources, resource end ---- Add mission type and number of required assets to resource. +--- Add mission type and number of required assets to resource list. -- @param #CHIEF self --- @param #table Resource Resource table. +-- @param #CHIEF.Resources Resource List of resources. -- @param #string MissionType Mission Type. --- @param #number Nmin Min number of required assets. --- @param #number Nmax Max number of requried assets. +-- @param #number Nmin Min number of required assets. Default 1. +-- @param #number Nmax Max number of requried assets. Default equal `Nmin`. -- @param #table Attributes Generalized attribute(s). -- @param #table Properties DCS attribute(s). Default `nil`. --- @return #CHIEF self +-- @return #CHIEF.Resource Resource table. function CHIEF:AddToResource(Resource, MissionType, Nmin, Nmax, Attributes, Properties) - - -- Ensure table. - if Attributes and type(Attributes)~="table" then - Attributes={Attributes} - end - - -- Ensure table. - if Properties and type(Properties)~="table" then - Properties={Properties} - end - + -- Create new resource table. local resource={} --#CHIEF.Resource resource.MissionType=MissionType resource.Nmin=Nmin or 1 - resource.Nmax=Nmax or 1 - resource.Attributes=Attributes or {} - resource.Properties=Properties or {} + resource.Nmax=Nmax or Nmin + resource.Attributes=UTILS.EnsureTable(Attributes) + resource.Properties=UTILS.EnsureTable(Properties) + + -- Transport carrier parameters. + resource.cargoAttributes=nil + resource.cargoProperties=nil + resource.cargoCategories=nil + resource.carrierNmin=nil + resource.carrierNmax=nil + resource.carrierAttributes=nil + resource.carrierProperties=nil + resource.carrierCategories=nil -- Add to table. table.insert(Resource, resource) @@ -788,6 +804,32 @@ function CHIEF:AddToResource(Resource, MissionType, Nmin, Nmax, Attributes, Prop self:I(self.lid..text) end + return resource +end + +--- Define which assets will be transported and define the number and attributes/properties of the cargo carrier assets. +-- @param #CHIEF self +-- @param #CHIEF.Resource Resource Resource table. +-- @param #table CargoAttributes Generalized attribute(s) of the cargo assets. +-- @param #table CargoProperties DCS attribute(s) of the cargo assets. +-- @param #table CargoCategories Group categories of the cargo assets. +-- @param #number Nmin Min number of required assets. Default 1. +-- @param #number Nmax Max number of requried assets. Default is equal to `Nmin`. +-- @param #table CarrierAttributes Generalized attribute(s) of the carrier assets. +-- @param #table CarrierProperties DCS attribute(s) of the carrier assets. +-- @param #table CarrierCategories Group categories of the carrier assets. +-- @return #CHIEF self +function CHIEF:AddTransportToResource(Resource, CargoAttributes, CargoProperties, CargoCategories, Nmin, Nmax, CarrierAttributes, CarrierProperties, CarrierCategories) + + Resource.cargoCategories=CargoCategories + Resource.cargoAttributes=CargoAttributes + Resource.cargoProperties=CargoProperties + Resource.carrierNmin=Nmin or 1 + Resource.carrierNmax=Nmax or Nmin + Resource.carrierCategories=CarrierCategories + Resource.carrierAttributes=CarrierAttributes + Resource.carrierProperties=CarrierProperties + return self end @@ -1193,17 +1235,17 @@ end -- -- Empty: -- --- * `AUFTRAG.Type.ONGUARD` with Nmin=1 and Nmax=3 assets, Attribute=`GROUP.Attribute.GROUND_INFANTRY`. -- * `AUFTRAG.Type.ONGURAD` with Nmin=1 and Nmax=1 assets, Attribute=`GROUP.Attribute.GROUND_TANK`. +-- * `AUFTRAG.Type.ONGUARD` with Nmin=1 and Nmax=3 assets, Attribute=`GROUP.Attribute.GROUND_INFANTRY`. -- -- Resources can be created with the @{#CHIEF.CreateResource} and @{#CHIEF.AddToResource} functions. -- -- @param #CHIEF self -- @param Ops.OpsZone#OPSZONE OpsZone OPS zone object. -- @param #number Priority Priority. Default 50. --- @param #number Importance Importance. Default nil. --- @param #CHIEF.Resource ResourceOccupied (Optional) Resources used then zone is occupied by the enemy. --- @param #CHIEF.Resource ResourceEmpty (Optional) Resources used then zone is empty. +-- @param #number Importance Importance. Default `#nil`. +-- @param #CHIEF.Resources ResourceOccupied (Optional) Resources used then zone is occupied by the enemy. +-- @param #CHIEF.Resources ResourceEmpty (Optional) Resources used then zone is empty. -- @return #CHIEF.StrategicZone The strategic zone. function CHIEF:AddStrategicZone(OpsZone, Priority, Importance, ResourceOccupied, ResourceEmpty) @@ -1232,8 +1274,11 @@ function CHIEF:AddStrategicZone(OpsZone, Priority, Importance, ResourceOccupied, if ResourceEmpty then stratzone.resourceEmpty=UTILS.DeepCopy(ResourceEmpty) else - stratzone.resourceEmpty=self:CreateResource(AUFTRAG.Type.ONGUARD, 1, 3, GROUP.Attribute.GROUND_INFANTRY) - self:AddToResource(stratzone.resourceEmpty, AUFTRAG.Type.ONGUARD, 1, 1, GROUP.Attribute.GROUND_TANK) + local resourceEmpty, resourceInfantry=self:CreateResource(AUFTRAG.Type.ONGUARD, 1, 3, GROUP.Attribute.GROUND_INFANTRY) + self:AddToResource(resourceEmpty, AUFTRAG.Type.ONGUARD, 0, 1, GROUP.Attribute.GROUND_TANK) + self:AddToResource(resourceEmpty, AUFTRAG.Type.ONGUARD, 0, 1, GROUP.Attribute.GROUND_IFV) + self:AddTransportToResource(resourceInfantry, GROUP.Attribute.GROUND_INFANTRY, nil, nil, 0, 1, {GROUP.Attribute.AIR_TRANSPORTHELO, GROUP.Attribute.GROUND_APC}) + stratzone.resourceEmpty=resourceEmpty end -- Add to table. @@ -1438,7 +1483,7 @@ end -- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.DEFENSIVE`. -- -- @param #CHIEF self --- @param Core.Set#SET_ZONE BorderZoneSet Set of zones, defining our borders. +-- @param Core.Set#SET_ZONE BorderZoneSet Set of zones defining our borders. -- @return #CHIEF self function CHIEF:SetBorderZones(BorderZoneSet) @@ -1454,7 +1499,7 @@ end -- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.DEFENSIVE`. -- -- @param #CHIEF self --- @param Core.Zone#ZONE Zone The zone. +-- @param Core.Zone#ZONE Zone The zone to be added. -- @return #CHIEF self function CHIEF:AddBorderZone(Zone) @@ -1464,6 +1509,18 @@ function CHIEF:AddBorderZone(Zone) return self end +--- Remove a border zone defining your territory. +-- @param #CHIEF self +-- @param Core.Zone#ZONE Zone The zone to be removed. +-- @return #CHIEF self +function CHIEF:RemoveBorderZone(Zone) + + -- Add a border zone. + self.borderzoneset:Remove(Zone:GetName()) + + return self +end + --- Set conflict zone set. -- -- * Detected enemy troops in these zones will trigger defence condition `YELLOW`. @@ -1486,7 +1543,7 @@ end -- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.OFFENSIVE`. -- -- @param #CHIEF self --- @param Core.Zone#ZONE Zone The zone to add. +-- @param Core.Zone#ZONE Zone The zone to be added. -- @return #CHIEF self function CHIEF:AddConflictZone(Zone) @@ -1496,6 +1553,19 @@ function CHIEF:AddConflictZone(Zone) return self end +--- Remove a conflict zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE Zone The zone to be removed. +-- @return #CHIEF self +function CHIEF:RemoveConflictZone(Zone) + + -- Add a conflict zone. + self.yellowzoneset:Remove(Zone:GetName()) + + return self +end + + --- Set attack zone set. -- -- * Enemies in these zones will only be engaged if strategy is at least `CHIEF.STRATEGY.AGGRESSIVE`. @@ -1526,11 +1596,24 @@ function CHIEF:AddAttackZone(Zone) return self end +--- Remove an attack zone. +-- @param #CHIEF self +-- @param Core.Zone#ZONE Zone The zone to be removed. +-- @return #CHIEF self +function CHIEF:RemoveAttackZone(Zone) + + -- Add an attack zone. + self.engagezoneset:Remove(Zone:GetName()) + + return self +end + --- Allow chief to use GROUND units for transport tasks. Helicopters are still preferred, and be aware there's no check as of now -- if a destination can be reached on land. -- @param #CHIEF self -- @return #CHIEF self function CHIEF:AllowGroundTransport() + env.warning("WARNING: CHIEF:AllowGroundTransport() is depricated and will be removed in the future!") self.TransportCategories = {Group.Category.GROUND, Group.Category.HELICOPTER} return self end @@ -1539,6 +1622,7 @@ end -- @param #CHIEF self -- @return #CHIEF self function CHIEF:ForbidGroundTransport() + env.warning("WARNING: CHIEF:ForbidGroundTransport() is depricated and will be removed in the future!") self.TransportCategories = {Group.Category.HELICOPTER} return self end @@ -2114,7 +2198,16 @@ function CHIEF:_TacticalOverview() for _,_stratzone in pairs(self.zonequeue) do local stratzone=_stratzone --#CHIEF.StrategicZone local owner=stratzone.opszone:GetOwnerName() - text=text..string.format(" - %s: %s - %s [I=%d, P=%d]\n", stratzone.opszone:GetName(), owner, stratzone.opszone:GetState(), stratzone.importance, stratzone.prio) + text=text..string.format(" - %s: %s - %s [I=%d, P=%d]\n", stratzone.opszone:GetName(), owner, stratzone.opszone:GetState(), stratzone.importance or 0, stratzone.prio or 0) + end + + local Ntransports=#self.commander.transportqueue + if Ntransports>0 then + text=text..string.format("Transports: %d\n", Ntransports) + for _,_transport in pairs(self.commander.transportqueue) do + local transport=_transport --Ops.OpsTransport#OPSTRANSPORT + text=text..string.format(" - %s", transport:GetState()) + end end -- Message to coalition. @@ -2867,8 +2960,35 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) -- Debug messgage. self:T2(self.lid..string.format("Recruited %d assets for %s mission STRATEGIC zone %s", #assets, MissionType, tostring(StratZone.opszone.zoneName))) + -- Short cuts. local TargetZone = StratZone.opszone.zone - local TargetCoord = TargetZone:GetCoordinate() + local TargetCoord = TargetZone:GetCoordinate() + + -- First check if we need a transportation. + local transport=nil + if Resource.carrierNmin and Resource.carrierNmax and Resource.carrierNmax>0 then + + -- Filter only those assets that shall be transported. + local cargoassets=CHIEF._FilterAssets(assets, Resource.cargoCategories, Resource.cargoAttributes, Resource.cargoProperties) + + if #cargoassets>0 then + + -- Recruit transport carrier assets. + recruited, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, cargoassets, + Resource.carrierNmin, Resource.carrierNmax, TargetZone, nil, Resource.carrierCategories, Resource.carrierAttributes) + + end + + end + + -- Check if everything was recruited. + if not recruited then + -- No (transport) assets ==> no mission! + self:T(self.lid..string.format("Could not allocate assets or transport of OPSZONE!")) + LEGION.UnRecruitAssets(assets) + return false + end + if MissionType==AUFTRAG.Type.PATROLZONE or MissionType==AUFTRAG.Type.ONGUARD then @@ -2878,48 +2998,16 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) -- Debug messgage. self:T2(self.lid..string.format("Recruited %d assets for PATROL mission", #assets)) - - -- First check if we need a transportation. - local recruitedTrans=true ; local transport=nil - if Attributes and Attributes[1]==GROUP.Attribute.GROUND_INFANTRY then - - -- Categories. Currently only helicopters are allowed due to problems with ground transports (might get stuck, might not be a land connection. - -- TODO: Check if ground transport is possible. For example, by trying land.getPathOnRoad or something. - local Categories=self.TransportCategories - - -- Recruit transport assets for infantry. - recruitedTrans, transport=LEGION.AssignAssetsForTransport(self.commander, self.commander.legions, assets, 1, 1, TargetZone, nil, Categories) + if MissionType==AUFTRAG.Type.PATROLZONE then + mission=AUFTRAG:NewPATROLZONE(TargetZone) + + elseif MissionType==AUFTRAG.Type.ONGUARD then + mission=AUFTRAG:NewONGUARD(TargetZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND})) end - if recruitedTrans then - - if MissionType==AUFTRAG.Type.PATROLZONE then - mission=AUFTRAG:NewPATROLZONE(TargetZone) - - elseif MissionType==AUFTRAG.Type.ONGUARD then - mission=AUFTRAG:NewONGUARD(TargetZone:GetRandomCoordinate(nil, nil, {land.SurfaceType.LAND})) - end - - -- Engage detected targets. - mission:SetEngageDetected(25, {"Ground Units", "Light armed ships", "Helicopters"}) - - -- Attach OPS transport to mission. - mission.opstransport=transport - - -- Set ops zone to transport. - if transport then - transport.opszone=StratZone.opszone - transport.chief=self - transport.commander=self.commander - end - - else - -- No transport ==> no mission! - self:T(self.lid..string.format("Could not allocate transport of OPSZONE infantry!")) - LEGION.UnRecruitAssets(assets) - return false - end + -- Engage detected targets. + mission:SetEngageDetected(25, {"Ground Units", "Light armed ships", "Helicopters"}) elseif MissionType==AUFTRAG.Type.CASENHANCED then @@ -3042,6 +3130,15 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) -- Attach mission to resource. Resource.mission=mission + + if transport then + -- Attach OPS transport to mission. + mission.opstransport=transport + -- Set ops zone to transport. + transport.opszone=StratZone.opszone + transport.chief=self + transport.commander=self.commander + end return true else @@ -3061,6 +3158,89 @@ function CHIEF:RecruitAssetsForZone(StratZone, Resource) return false end +--- Filter assets, which have certain categories, attributes and/or properties. +-- @param #table Assets The assets to be filtered. +-- @param #table Categories Group categories. +-- @param #table Attributes Generalized attributes. +-- @param #table Properties DCS attributes +-- @return #table Table of filtered assets. +function CHIEF._FilterAssets(Assets, Categories, Attributes, Properties) + + local filtered={} + + for _,_asset in pairs(Assets) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + + local hasCat=CHIEF._CheckAssetCategories(asset, Categories) + local hasAtt=CHIEF._CheckAssetAttributes(asset, Attributes) + local hasPro=CHIEF._CheckAssetProperties(asset, Properties) + + if hasAtt and hasCat and hasPro then + table.insert(filtered, asset) + end + + end + + return filtered +end + +--- Check if a given asset has certain attribute(s). +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset item. +-- @param #table Attributes The required attributes. See `WAREHOUSE.Attribute` enum. Can also be passed as a single attribute `#string`. +-- @return #boolean Returns `true`, the asset has at least one requested attribute. +function CHIEF._CheckAssetAttributes(Asset, Attributes) + + if not Attributes then + return true + end + + for _,attribute in pairs(UTILS.EnsureTable(Attributes)) do + if attribute==Asset.attribute then + return true + end + end + + return false +end + +--- Check if a given asset has certain categories. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset item. +-- @param #table Categories DCS group categories. +-- @return #boolean Returns `true`, the asset has at least one requested category. +function CHIEF._CheckAssetCategories(Asset, Categories) + + if not Categories then + return true + end + + for _,attribute in pairs(UTILS.EnsureTable(Categories)) do + if attribute==Asset.category then + return true + end + end + + return false +end + +--- Check if a given asset has certain properties. +-- @param Functional.Warehouse#WAREHOUSE.Assetitem Asset The asset item. +-- @param #table Categories DCS group categories. +-- @return #boolean Returns `true`, the asset has at least one requested property. +function CHIEF._CheckAssetProperties(Asset, Properties) + + if not Properties then + return true + end + + for _,attribute in pairs(UTILS.EnsureTable(Properties)) do + if attribute==Asset.DCSdesc then + return true + end + end + + return false +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 29271d3fd..45fd1e85b 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -210,7 +210,7 @@ FLIGHTGROUP.Players={} --- FLIGHTGROUP class version. -- @field #string version -FLIGHTGROUP.version="0.8.1" +FLIGHTGROUP.version="0.8.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -899,6 +899,60 @@ function FLIGHTGROUP:Status() end end + -- Get current mission (if any). + local mission=self:GetMissionCurrent() + + -- If mission, check if DCS task needs to be updated. + if mission and mission.updateDCSTask then + + -- Orbit missions might need updates. + if (mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER) and mission.orbitVec2 then + + -- Get 2D vector of orbit target. + local vec2=mission:GetTargetVec2() + + -- Heading. + local hdg=mission:GetTargetHeading() + + -- Heading change? + local hdgchange=false + if mission.orbitLeg then + if UTILS.HdgDiff(hdg, mission.targetHeading)>0 then + hdgchange=true + end + end + + -- Distance to previous position. + local dist=UTILS.VecDist2D(vec2, mission.orbitVec2) + + -- Distance change? + local distchange=dist>mission.orbitDeltaR + + -- Debug info. + self:T3(self.lid..string.format("Checking orbit mission dist=%d meters", dist)) + + -- Check if distance is larger than threshold. + if distchange or hdgchange then + + -- Debug info. + self:T3(self.lid..string.format("Updating orbit!")) + + -- Update DCS task. This also sets the new mission.orbitVec2. + local DCSTask=mission:GetDCSMissionTask() --DCS#Task + + -- Get task. + local Task=self:GetTaskByID(mission.auftragsnummer) + + -- Reset current orbit task. + self.controller:resetTask() + + -- Push task after one second. We need to give resetTask some time or it will not work! + self:_SandwitchDCSTask(DCSTask, Task, false, 1) + + end + end + end + -- TODO: _CheckParking() function @@ -1156,16 +1210,6 @@ function FLIGHTGROUP:Status() -- Current mission. local mission=self:GetMissionCurrent() - - if mission and mission.type==AUFTRAG.Type.RECOVERYTANKER and mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then - - --env.info("FF recovery tanker updating DCS task") - --self:ClearTasks() - - local DCSTask=mission:GetDCSMissionTask() - self:SetTask(DCSTask) - - end end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 54dcedea6..05d6c6b10 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -499,7 +499,7 @@ OPSGROUP.CargoStatus={ --- OpsGroup version. -- @field #string version -OPSGROUP.version="0.7.9" +OPSGROUP.version="0.8.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -4070,6 +4070,23 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Get mission of this task (if any). local Mission=self:GetMissionByTaskID(self.taskcurrent) + + self:_UpdateTask(Task, Mission) + + -- Set AUFTRAG status. + if Mission then + self:MissionExecute(Mission) + end + +end + +--- Push task +-- @param #OPSGROUP self +-- @param Ops.OpsGroup#OPSGROUP.Task Task The task. +function OPSGROUP:_UpdateTask(Task, Mission) + + local Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) + if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then -- Set of group(s) to follow Mother. @@ -4421,52 +4438,14 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Set quantity of task. DCSTask.params.expendQty=nShots - elseif Mission and Mission.type==AUFTRAG.Type.RECOVERYTANKER then - - env.info("FF recoverytanker setting DCS task") - - -- Update DCS task with the current carrier parameters. - DCSTask=Mission:GetDCSMissionTask() - else --- -- Take DCS task --- DCSTask=Task.dcstask end - - local DCStasks={} - if DCSTask.id=='ComboTask' then - -- Loop over all combo tasks. - for TaskID, Task in ipairs(DCSTask.params.tasks) do - table.insert(DCStasks, Task) - end - else - table.insert(DCStasks, DCSTask) - end - - -- Combo task. - local TaskCombo=self.group:TaskCombo(DCStasks) - - -- Stop condition! - local TaskCondition=self.group:TaskCondition(nil, Task.stopflag:GetName(), 1, nil, Task.duration) - - -- Controlled task. - local TaskControlled=self.group:TaskControlled(TaskCombo, TaskCondition) - - -- Task done. - local TaskDone=self.group:TaskFunction("OPSGROUP._TaskDone", self, Task) - - -- Final task. - local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) - - -- Set task for group. - -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. - -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles - -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. - self:PushTask(TaskFinal) - --self:SetTask(TaskFinal) - + + self:_SandwitchDCSTask(DCSTask, Task) elseif Task.type==OPSGROUP.TaskType.WAYPOINT then -- Waypoint tasks are executed elsewhere! @@ -4475,15 +4454,62 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) end end + +end +--- Sandwitch DCS task in stop condition and push the task to the group. +-- @param #OPSGROUP self +-- @param DCS#Task DCSTask The DCS task. +-- @param Ops.OpsGroup#OPSGROUP.Task Task +-- @param #boolean SetTask Set task instead of pushing it. +-- @param #number Delay Delay in seconds. Default nil. +function OPSGROUP:_SandwitchDCSTask(DCSTask, Task, SetTask, Delay) - -- Set AUFTRAG status. - if Mission then - self:MissionExecute(Mission) + if Delay and Delay>0 then + -- Delayed call. + self:ScheduleOnce(Delay, OPSGROUP._SandwitchDCSTask, self, DCSTask, Task, SetTask) + else + + local DCStasks={} + if DCSTask.id=='ComboTask' then + -- Loop over all combo tasks. + for TaskID, Task in ipairs(DCSTask.params.tasks) do + table.insert(DCStasks, Task) + end + else + table.insert(DCStasks, DCSTask) + end + + -- Combo task. + local TaskCombo=self.group:TaskCombo(DCStasks) + + -- Stop condition! + local TaskCondition=self.group:TaskCondition(nil, Task.stopflag:GetName(), 1, nil, Task.duration) + + -- Controlled task. + local TaskControlled=self.group:TaskControlled(TaskCombo, TaskCondition) + + -- Task done. + local TaskDone=self.group:TaskFunction("OPSGROUP._TaskDone", self, Task) + + -- Final task. + local TaskFinal=self.group:TaskCombo({TaskControlled, TaskDone}) + + -- Set task for group. + -- NOTE: I am pushing the task instead of setting it as it seems to keep the mission task alive. + -- There were issues that flights did not proceed to a later waypoint because the task did not finish until the fired missiles + -- impacted (took rather long). Then the flight flew to the nearest airbase and one lost completely the control over the group. + if SetTask then + self:SetTask(TaskFinal) + else + self:PushTask(TaskFinal) + end + end end + --- On after "TaskCancel" event. Cancels the current task or simply sets the status to DONE if the task is not the current one. -- @param #OPSGROUP self -- @param #string From From state. @@ -5534,23 +5560,7 @@ function OPSGROUP:RouteToMission(mission, delay) -- Navy group: Route into direction of the target. waypointcoord=currentcoord:GetIntermediateCoordinate(ToCoordinate, 0.05) end - - elseif mission.type==AUFTRAG.Type.RECOVERYTANKER then - --- - -- Recoverytanker - --- - local carrier=mission.DCStask.params.carrier --Wrapper.Unit#UNIT - - -- Roughly go to the new legion. - local CarrierCoordinate=carrier:GetCoordinate() - - local heading=carrier:GetHeading() - - waypointcoord=CarrierCoordinate:Translate(10000, heading-180):SetAltitude(2000) - - waypointcoord:MarkToAll("Recoverytanker") - else --- -- Default case @@ -5640,20 +5650,35 @@ function OPSGROUP:RouteToMission(mission, delay) end end - + + + -- Distance to waypoint coordinate. + local d=currentcoord:Get2DDistance(waypointcoord) + + -- Debug info. + self:T(self.lid..string.format("Distance to ingress waypoint=%.1f m", d)) -- Add mission execution (ingress) waypoint. local waypoint=nil --#OPSGROUP.Waypoint if self:IsFlightgroup() then + waypoint=FLIGHTGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + elseif self:IsArmygroup() then + + -- Set formation. local formation=mission.optionFormation - if mission.type==AUFTRAG.Type.RELOCATECOHORT then + + -- If distance is < 1 km or RELOCATECOHORT mission, go off-road. + if d<1000 or mission.type==AUFTRAG.Type.RELOCATECOHORT then formation=ENUMS.Formation.Vehicle.OffRoad end + waypoint=ARMYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, formation, false) elseif self:IsNavygroup() then + waypoint=NAVYGROUP.AddWaypoint(self, waypointcoord, SpeedToMission, uid, UTILS.MetersToFeet(mission.missionAltitude or self.altitudeCruise), false) + end waypoint.missionUID=mission.auftragsnummer @@ -5681,13 +5706,6 @@ function OPSGROUP:RouteToMission(mission, delay) Ewaypoint.missionUID=mission.auftragsnummer mission:SetGroupEgressWaypointUID(self, Ewaypoint.uid) end - - - -- Distance to waypoint coordinate. - local d=currentcoord:Get2DDistance(waypointcoord) - - -- Debug info. - self:T(self.lid..string.format("FF distance to ingress waypoint=%.1f m", d)) -- Check if we are already where we want to be. if targetzone and self:IsInZone(targetzone) then @@ -8499,13 +8517,9 @@ function OPSGROUP:onafterPickup(From, Event, To) -- Get a random coordinate in the pickup zone and let the carrier go there. local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) - --Coordinate:MarkToAll(string.format("Pickup coordinate for group %s [Surface type=%d]", self:GetName(), Coordinate:GetSurfaceType())) - - -- Current Waypoint. - local cwp=self:GetWaypointCurrent() -- Current waypoint ID. - local uid=cwp and cwp.uid or nil + local uid=self:GetWaypointCurrentUID() -- Add waypoint. if self:IsFlightgroup() then @@ -8546,8 +8560,6 @@ function OPSGROUP:onafterPickup(From, Event, To) local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate, 0.5) - --coordinate:MarkToAll("Pickup Inter Coord") - -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. local waypoint=FLIGHTGROUP.AddWaypoint(self, coordinate, nil, uid, UTILS.MetersToFeet(self.altitudeCruise), true) ; waypoint.detour=1 @@ -8616,8 +8628,8 @@ function OPSGROUP:onafterPickup(From, Event, To) local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) -- Formation used to go to the pickup zone.. - local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC) - + local Formation=self.cargoTransport:_GetFormationPickup(self.cargoTZC, self) + -- Get transport path. if path and oldstatus~=OPSGROUP.CarrierStatus.NOTCARRIER then for i=#path.waypoints,1,-1 do @@ -8632,7 +8644,7 @@ function OPSGROUP:onafterPickup(From, Event, To) local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid, Formation, false) ; waypoint.detour=1 -- Give cruise command. - self:__Cruise(-2) + self:__Cruise(-2, nil, Formation) end @@ -8917,7 +8929,8 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Coord where the carrier goes to unload. local Coordinate=Zone:GetRandomCoordinate(nil, nil, surfacetypes) --Core.Point#COORDINATE - --Coordinate:MarkToAll(string.format("Deploy coordinate for group %s [Surface type=%d]", self:GetName(), Coordinate:GetSurfaceType())) + -- Current waypoint UID. + local uid=self:GetWaypointCurrentUID() -- Add waypoint. if self:IsFlightgroup() then @@ -8933,9 +8946,6 @@ function OPSGROUP:onafterTransport(From, Event, To) -- Deploy at airbase --- - local cwp=self:GetWaypointCurrent() - local uid=cwp and cwp.uid or nil - -- Get a (random) pre-defined transport path. local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) @@ -8957,16 +8967,11 @@ function OPSGROUP:onafterTransport(From, Event, To) local coordinate=self:GetCoordinate():GetIntermediateCoordinate(Coordinate, 0.5) - --coordinate:MarkToAll("Transport Inter Waypoint") - -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. local waypoint=FLIGHTGROUP.AddWaypoint(self, coordinate, nil, uid, UTILS.MetersToFeet(self.altitudeCruise), true) ; waypoint.detour=1 end - -- Order group to land at an airbase. - --self:__LandAtAirbase(-0.1, airbaseDeploy) - elseif self.isHelo then --- @@ -8974,7 +8979,7 @@ function OPSGROUP:onafterTransport(From, Event, To) --- -- If this is a helo and no ZONE_AIRBASE was given, we make the helo land in the pickup zone. - local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, self:GetWaypointCurrent().uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 + local waypoint=FLIGHTGROUP.AddWaypoint(self, Coordinate, nil, uid, UTILS.MetersToFeet(self.altitudeCruise), false) ; waypoint.detour=1 else self:T(self.lid.."ERROR: Aircraft (cargo carrier) cannot land in Deploy zone! Specify a ZONE_AIRBASE as deploy zone") @@ -8992,14 +8997,11 @@ function OPSGROUP:onafterTransport(From, Event, To) elseif self:IsArmygroup() then - local cwp=self:GetWaypointCurrent() - local uid=cwp and cwp.uid or nil - -- Get transport path. local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) -- Formation used for transporting. - local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC) + local Formation=self.cargoTransport:_GetFormationTransport(self.cargoTZC, self) -- Get transport path. if path then @@ -9015,13 +9017,10 @@ function OPSGROUP:onafterTransport(From, Event, To) local waypoint=ARMYGROUP.AddWaypoint(self, Coordinate, nil, uid, Formation, false) ; waypoint.detour=1 -- Give cruise command. - self:Cruise() + self:Cruise(nil, Formation) elseif self:IsNavygroup() then - local cwp=self:GetWaypointCurrent() - local uid=cwp and cwp.uid or nil - -- Get a (random) pre-defined transport path. local path=self.cargoTransport:_GetPathTransport(self.category, self.cargoTZC) @@ -9434,7 +9433,7 @@ function OPSGROUP:onafterDelivered(From, Event, To, CargoTransport) end else -- Army & Navy: give Cruise command to "wake up" from waiting status. - self:__Cruise(0.1) + self:__Cruise(-0.1) end -- Set carrier transport status. @@ -9859,14 +9858,14 @@ function OPSGROUP:_CheckGroupDone(delay) if delay and delay>0 then -- Debug info. - self:T(self.lid..string.format("Check OPSGROUP [state=%s] done in %.3f seconds...", fsmstate, delay)) + self:T(self.lid..string.format("Check OPSGROUP done? [state=%s] in %.3f seconds...", fsmstate, delay)) -- Delayed call. self:ScheduleOnce(delay, self._CheckGroupDone, self) else -- Debug info. - self:T(self.lid..string.format("Check OSGROUP [state=%s] done?", fsmstate)) + self:T(self.lid..string.format("Check OSGROUP done? [state=%s]", fsmstate)) -- Group is engaging something. if self:IsEngaging() then @@ -10646,7 +10645,7 @@ function OPSGROUP._PassingWaypoint(opsgroup, uid) -- Set formation. if opsgroup.isArmygroup then - opsgroup.formation=wpnext.action + opsgroup.option.Formation=wpnext.action end -- Set speed to next wp. diff --git a/Moose Development/Moose/Ops/OpsTransport.lua b/Moose Development/Moose/Ops/OpsTransport.lua index 633c3c81f..3db1707d1 100644 --- a/Moose Development/Moose/Ops/OpsTransport.lua +++ b/Moose Development/Moose/Ops/OpsTransport.lua @@ -50,6 +50,10 @@ -- @field #number NcarrierDead Total number of dead carrier groups -- @field #number NcargoDead Totalnumber of dead cargo groups. -- +-- @field #string formationArmy Default formation for ground vehicles. +-- @field #string formationHelo Default formation for helicopters. +-- @field #string formationPlane Default formation for airplanes. +-- -- @field Ops.Auftrag#AUFTRAG mission The mission attached to this transport. -- @field #table assets Warehouse assets assigned for this transport. -- @field #table legions Assigned legions. @@ -191,7 +195,7 @@ _OPSTRANSPORTID=0 --- Army Group version. -- @field #string version -OPSTRANSPORT.version="0.6.0" +OPSTRANSPORT.version="0.6.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -235,6 +239,10 @@ function OPSTRANSPORT:New(CargoGroups, PickupZone, DeployZone) self:SetTime() self:SetRequiredCarriers() + self.formationArmy=ENUMS.Formation.Vehicle.OnRoad + self.formationHelo=ENUMS.Formation.RotaryWing.Wedge + self.formationPlane=ENUMS.Formation.FixedWing.Wedge + -- Init arrays and counters. self.carriers={} self.Ncargo=0 @@ -809,16 +817,44 @@ function OPSTRANSPORT:SetFormationPickup(Formation, TransportZoneCombo) return self end +--- Get pickup formation. +-- @param #OPSTRANSPORT self +-- @param Ops.OpsGroup#OPSGROUP OpsGroup +-- @return #string Formation. +function OPSTRANSPORT:_GetFormationDefault(OpsGroup) + + if OpsGroup.isArmygroup then + + return self.formationArmy + + elseif OpsGroup.isFlightgroup then + + if OpsGroup.isHelo then + return self.formationHelo + else + return self.formationPlane + end + + else + return ENUMS.Formation.Vehicle.OffRoad + end + + return nil +end + --- Get pickup formation. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup -- @return #number Formation. -function OPSTRANSPORT:_GetFormationPickup(TransportZoneCombo) +function OPSTRANSPORT:_GetFormationPickup(TransportZoneCombo, OpsGroup) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + local formation=TransportZoneCombo.PickupFormation or self:_GetFormationDefault(OpsGroup) - return TransportZoneCombo.PickupFormation + return formation end --- Set transport formation. @@ -839,13 +875,16 @@ end --- Get transport formation. -- @param #OPSTRANSPORT self -- @param #OPSTRANSPORT.TransportZoneCombo TransportZoneCombo Transport zone combo. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup -- @return #number Formation. -function OPSTRANSPORT:_GetFormationTransport(TransportZoneCombo) +function OPSTRANSPORT:_GetFormationTransport(TransportZoneCombo, OpsGroup) -- Use default TZC if no transport zone combo is provided. TransportZoneCombo=TransportZoneCombo or self.tzcDefault + + local formation=TransportZoneCombo.TransportFormation or self:_GetFormationDefault(OpsGroup) - return TransportZoneCombo.TransportFormation + return formation end diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 40d0d7623..3968b7ee9 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -641,10 +641,8 @@ function OPSZONE:onafterStop(From, Event, To) -- Reinit the timer. self.timerStatus:Stop() - -- Draw zone. - if self.drawZone then - self.zone:UndrawZone() - end + -- Undraw zone. + self.zone:UndrawZone() -- Remove marker. if self.markZone then @@ -691,6 +689,11 @@ function OPSZONE:Status() -- Update F10 marker (only if enabled). self:_UpdateMarker() + -- Undraw zone. + if self.zone.DrawID and not self.drawZone then + self.zone:UndrawZone() + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index bb4b550dd..012eb7542 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -151,7 +151,7 @@ _TARGETID=0 --- TARGET class version. -- @field #string version -TARGET.version="0.5.5" +TARGET.version="0.5.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1108,7 +1108,7 @@ function TARGET:GetTargetLife(Target) return self end ---- Get current life points. +--- Get current total life points. This is the sum of all target objects. -- @param #TARGET self -- @return #number Life points of target. function TARGET:GetLife() @@ -1311,6 +1311,89 @@ function TARGET:GetTargetVec3(Target, Average) self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get Vec3") end +--- Get heading of the target. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return #number Heading in degrees. +function TARGET:GetTargetHeading(Target) + + if Target.Type==TARGET.ObjectType.GROUP then + + local object=Target.Object --Wrapper.Group#GROUP + + if object and object:IsAlive() then + local heading=object:GetHeading() + + if heading then + return heading + else + return nil + end + else + + return nil + + end + + elseif Target.Type==TARGET.ObjectType.UNIT then + + local object=Target.Object --Wrapper.Unit#UNIT + + if object and object:IsAlive() then + local heading=object:GetHeading() + return heading + else + return nil + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + + local object=Target.Object --Wrapper.Static#STATIC + + if object and object:IsAlive() then + local heading=object:GetHeading() + return heading + else + return nil + end + + elseif Target.Type==TARGET.ObjectType.SCENERY then + + local object=Target.Object --Wrapper.Scenery#SCENERY + + if object then + local heading=object:GetHeading() + return heading + else + return nil + end + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + + local object=Target.Object --Wrapper.Airbase#AIRBASE + + -- Airbase has no real heading. Return 0. Maybe take the runway heading? + return 0 + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + local object=Target.Object --Core.Point#COORDINATE + + -- A coordinate has no heading. Return 0. + return 0 + + elseif Target.Type==TARGET.ObjectType.ZONE then + + local object=Target.Object --Core.Zone#ZONE + + -- A zone has no heading. Return 0. + return 0 + + end + + self:E(self.lid.."ERROR: Unknown TARGET type! Cannot get heading") +end + --- Get target coordinate. -- @param #TARGET self @@ -1473,7 +1556,7 @@ function TARGET:GetAverageCoordinate() for _,_target in pairs(self.targets) do local Target=_target --#TARGET.Object - local coordinate=self:GetTargetCoordinate(Target,true) + local coordinate=self:GetTargetCoordinate(Target, true) if coordinate then return coordinate @@ -1485,6 +1568,26 @@ function TARGET:GetAverageCoordinate() return nil end +--- Get heading of target. +-- @param #TARGET self +-- @return #number Heading of the target in degrees. +function TARGET:GetHeading() + + for _,_target in pairs(self.targets) do + local Target=_target --#TARGET.Object + + local heading=self:GetTargetHeading(Target) + + if heading then + return heading + end + + end + + self:E(self.lid..string.format("ERROR: Cannot get heading of target %s", tostring(self.name))) + return nil +end + --- Get category. -- @param #TARGET self -- @return #string Target category. See `TARGET.Category.X`, where `X=AIRCRAFT, GROUND`. @@ -1492,6 +1595,7 @@ function TARGET:GetCategory() return self.category end + --- Get target category. -- @param #TARGET self -- @param #TARGET.Object Target Target object. diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index d93cd50ab..beeaa9fff 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1259,7 +1259,7 @@ end --- (AIR) Orbit at a position with at a given altitude and speed. Optionally, a race track pattern can be specified. -- @param #CONTROLLABLE self --- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits. +-- @param Core.Point#COORDINATE Coord Coordinate at which the CONTROLLABLE orbits. Can also be given as a `DCS#Vec3` or `DCS#Vec2` object. -- @param #number Altitude Altitude in meters of the orbit pattern. Default y component of Coord. -- @param #number Speed Speed [m/s] flying the orbit pattern. Default 128 m/s = 250 knots. -- @param Core.Point#COORDINATE CoordRaceTrack (Optional) If this coordinate is specified, the CONTROLLABLE will fly a race-track pattern using this and the initial coordinate. @@ -1268,11 +1268,11 @@ function CONTROLLABLE:TaskOrbit( Coord, Altitude, Speed, CoordRaceTrack ) local Pattern = AI.Task.OrbitPattern.CIRCLE - local P1 = Coord:GetVec2() + local P1 = {x=Coord.x, y=Coord.z or Coord.y} local P2 = nil if CoordRaceTrack then Pattern = AI.Task.OrbitPattern.RACE_TRACK - P2 = CoordRaceTrack:GetVec2() + P2 = {x=CoordRaceTrack.x, y=CoordRaceTrack.z or CoordRaceTrack.y} end local Task = {