diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index ad178d931..014e5cda1 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1919,6 +1919,41 @@ do -- SET_GROUP end end + + --- Get the closest group of the set with respect to a given reference coordinate. Optionally, only groups of given coalitions are considered in the search. + -- @param #SET_GROUP self + -- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined. + -- @return Wrapper.Group#GROUP The closest group (if any). + -- @return #number Distance in meters to the closest group. + function SET_GROUP:GetClosestGroup(Coordinate, Coalitions) + + local Set = self:GetSet() + + local dmin=math.huge + local gmin=nil + + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + local group=GroupData --Wrapper.Group#GROUP + + if Coalitions==nil or UTILS.IsAnyInTable(Coalitions, group:GetCoalition()) then + + local coord=group:GetCoord() + + -- Distance between ref. coordinate and group coordinate. + local d=UTILS.VecDist3D(Coordinate, coord) + + if d Pausing mission!") self:PauseMission() dt=-0.1 @@ -1804,8 +1831,8 @@ function ARMYGROUP:_UpdateEngageTarget() -- Distance to last known position of target. local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) - -- Check if target moved more than 100 meters. - if dist>100 then + -- Check if target moved more than 100 meters or we do not have line of sight. + if dist>100 or not self:HasLoS(self.engage.Target:GetCoordinate()) then --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) @@ -1824,7 +1851,7 @@ function ARMYGROUP:_UpdateEngageTarget() self.engage.Waypoint=self:AddWaypoint(intercoord, self.engage.Speed, uid, self.engage.Formation, true) -- Set if we want to resume route after reaching the detour waypoint. - self.engage.Waypoint.detour=0 + self.engage.Waypoint.detour=0 end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 59ad141d7..971423f95 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -422,7 +422,8 @@ _AUFTRAGSNR=0 -- @field #string AIRDEFENSE Air defense. -- @field #string EWR Early Warning Radar. -- @field #string RECOVERYTANKER Recovery tanker. --- @filed #string REARMING Rearming mission. +-- @field #string REARMING Rearming mission. +-- @field #string CAPTUREZONE Capture zone mission. -- @field #string NOTHING Nothing. AUFTRAG.Type={ ANTISHIP="Anti Ship", @@ -465,6 +466,7 @@ AUFTRAG.Type={ EWR="Early Warning Radar", RECOVERYTANKER="Recovery Tanker", REARMING="Rearming", + CAPTUREZONE="Capture Zone", NOTHING="Nothing", } @@ -487,6 +489,7 @@ AUFTRAG.Type={ -- @field #string EWR Early Warning Radar. -- @field #string RECOVERYTANKER Recovery tanker. -- @field #string REARMING Rearming. +-- @field #string CAPTUREZONE Capture OPS zone. -- @field #string NOTHING Nothing. AUFTRAG.SpecialTask={ FORMATION="Formation", @@ -507,6 +510,7 @@ AUFTRAG.SpecialTask={ EWR="Early Warning Radar", RECOVERYTANKER="Recovery Tanker", REARMING="Rearming", + CAPTUREZONE="Capture Zone", NOTHING="Nothing", } @@ -1964,6 +1968,51 @@ function AUFTRAG:NewPATROLZONE(Zone, Speed, Altitude, Formation) return mission end +--- **[AIR, GROUND, NAVAL]** Create a CAPTUREZONE mission. Group(s) will go to the zone and patrol it randomly. +-- @param #AUFTRAG self +-- @param Ops.OpsZone#OPSZONE OpsZone The OPS zone to capture. +-- @param #number Coalition The coalition which should capture the zone for the mission to be successful. +-- @param #number Speed Speed in knots. +-- @param #number Altitude Altitude in feet. Only for airborne units. Default 2000 feet ASL. +-- @param #string Formation Formation used by ground units during patrol. Default "Off Road". +-- @return #AUFTRAG self +function AUFTRAG:NewCAPTUREZONE(OpsZone, Coalition, Speed, Altitude, Formation) + + local mission=AUFTRAG:New(AUFTRAG.Type.CAPTUREZONE) + + + mission:_TargetFromObject(OpsZone) + + mission.coalition=Coalition + + mission.missionTask=mission:GetMissionTaskforMissionType(AUFTRAG.Type.CAPTUREZONE) + + mission.optionROE=ENUMS.ROE.OpenFire + mission.optionROT=ENUMS.ROT.PassiveDefense + mission.optionAlarm=ENUMS.AlarmState.Auto + + mission.missionFraction=1.0 + mission.missionSpeed=Speed and UTILS.KnotsToKmph(Speed) or nil + mission.missionAltitude=Altitude and UTILS.FeetToMeters(Altitude) or nil + + mission.categories={AUFTRAG.Category.ALL} + + mission.DCStask=mission:GetDCSMissionTask() + + mission.updateDCSTask=true + + local params={} + + params.formation=Formation or "Off Road" + params.zone=mission:GetObjective() + params.altitude=mission.missionAltitude + params.speed=mission.missionSpeed + + mission.DCStask.params=params + + return mission +end + --- **[OBSOLETE]** Create a ARMORATTACK mission. -- ** Note that this is actually creating a GROUNDATTACK mission!** @@ -4947,8 +4996,11 @@ function AUFTRAG:CountMissionTargets() local N=0 + -- Count specific coalitions. + local Coalitions=self.coalition and UTILS.GetCoalitionEnemy(self.coalition, true) or nil + if self.engageTarget then - N=self.engageTarget:CountTargets() + N=self.engageTarget:CountTargets(Coalitions) end return N @@ -5013,9 +5065,13 @@ end --- Get mission objective object. Could be many things depending on the mission type. -- @param #AUFTRAG self +-- @param Core.Point#COORDINATE RefCoordinate (Optional) Reference coordinate from which the closest target is determined. +-- @param #table Coalitions (Optional) Only consider targets of the given coalition(s). -- @return Wrapper.Positionable#POSITIONABLE The target object. Could be many things. -function AUFTRAG:GetObjective() - local objective=self:GetTargetData():GetObject() +function AUFTRAG:GetObjective(RefCoordinate, Coalitions) + + local objective=self:GetTargetData():GetObject(RefCoordinate, Coalitions) + return objective end @@ -5753,6 +5809,22 @@ function AUFTRAG:GetDCSMissionTask() DCStask.params=param table.insert(DCStasks, DCStask) + + elseif self.type==AUFTRAG.Type.CAPTUREZONE then + + -------------------------- + -- CAPTURE ZONE Mission -- + -------------------------- + + local DCStask={} + + DCStask.id=AUFTRAG.SpecialTask.CAPTUREZONE + + -- We create a "fake" DCS task and pass the parameters to the FLIGHTGROUP. + local param={} + DCStask.params=param + + table.insert(DCStasks, DCStask) elseif self.type==AUFTRAG.Type.CASENHANCED then diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 556fc5662..d08162f65 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -335,6 +335,7 @@ OPSGROUP.TaskType={ -- @field #number waypoint Waypoint index if task is a waypoint task. -- @field Core.UserFlag#USERFLAG stopflag If flag is set to 1 (=true), the task is stopped. -- @field #number backupROE Rules of engagement that are restored once the task is over. +-- @field Ops.Target#TARGET target Target object. --- Option data. -- @type OPSGROUP.Option @@ -4091,12 +4092,13 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) end ---- Push task. +--- Update (DCS) task. -- @param #OPSGROUP self -- @param Ops.OpsGroup#OPSGROUP.Task Task The task. +-- @param Ops.Auftrag#AUFTRAG Mission The mission. function OPSGROUP:_UpdateTask(Task, Mission) - local Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) + Mission=Mission or self:GetMissionByTaskID(self.taskcurrent) if Task.dcstask.id==AUFTRAG.SpecialTask.FORMATION then @@ -4378,6 +4380,64 @@ function OPSGROUP:_UpdateTask(Task, Mission) end wp.missionUID=Mission and Mission.auftragsnummer or nil + + elseif Task.dcstask.id==AUFTRAG.SpecialTask.CAPTUREZONE then + + --- + -- Task "CaptureZone" Mission. + --- + + env.info("FF Update Task Capture zone") + + -- Find the closest enemy group and engage! + + -- Not enganging already. + if self:IsEngaging() then + self:T(self.lid..string.format("Engaging currently!")) + else + + local Coalitions=UTILS.GetCoalitionEnemy(self:GetCoalition(), false) + + local zoneCurr=Task.target --Ops.OpsZone#OPSZONE + + if zoneCurr then + + self:T(self.lid..string.format("Current target zone=%s", zoneCurr:GetName())) + + if zoneCurr:GetOwner()==self:GetCoalition() then + -- Current zone captured ==> Find next zone or call it a day! + + self:T(self.lid..string.format("Zone %s captured ==> Task DONE!", zoneCurr:GetName())) + + self:TaskDone(Task) + + else + -- Current zone NOT captured yet ==> Find Target + + if Mission:GetGroupStatus(self)==AUFTRAG.GroupStatus.EXECUTING then + + -- Get closest target. + local targetgroup=zoneCurr:GetScannedGroupSet():GetClosestGroup(self.coordinate, Coalitions) + + if targetgroup then + + self:EngageTarget(targetgroup) + + else + -- Error Message. + self:E(self.lid..string.format("ERROR: Current zone not captured but no target group could be found. This should NOT happen!")) + end + + end + + end + + + else + self:T(self.lid..string.format("NO Current target zone=%s")) + end + + end else @@ -4662,9 +4722,18 @@ function OPSGROUP:onafterTaskDone(From, Event, To, Task) -- Check if mission is paused. if status~=AUFTRAG.GroupStatus.PAUSED then --- - -- Mission is NOT over ==> trigger done + -- Mission is NOT over ==> trigger DONE --- + if Mission.type==AUFTRAG.Type.CAPTUREZONE and Mission:CountMissionTargets()>0 then + + env.info("FF task done route to mission 1000") + self:RouteToMission(Mission) + + return + + end + -- Get egress waypoint uid. local EgressUID=Mission:GetGroupEgressWaypointUID(self) @@ -5496,6 +5565,13 @@ function OPSGROUP:RouteToMission(mission, delay) surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} end + -- Get target object. + local targetobject=mission:GetObjective(currentcoord, UTILS.GetCoalitionEnemy(self:GetCoalition(), true)) + + if targetobject then + self:T(self.lid..string.format("Route to mission target object %s", targetobject:GetName())) + end + -- Get ingress waypoint. if mission.opstransport and not mission.opstransport:IsCargoDelivered(self.groupname) then @@ -5526,7 +5602,7 @@ function OPSGROUP:RouteToMission(mission, delay) --- -- Get the zone. - targetzone=mission.engageTarget:GetObject() --Core.Zone#ZONE + targetzone=targetobject --Core.Zone#ZONE -- Random coordinate. waypointcoord=targetzone:GetRandomCoordinate(nil , nil, surfacetypes) @@ -5545,7 +5621,7 @@ function OPSGROUP:RouteToMission(mission, delay) --- -- Get the zone. - targetzone=mission.engageTarget:GetObject() --Core.Zone#ZONE + targetzone=targetobject --Core.Zone#ZONE -- Random coordinate. waypointcoord=targetzone:GetRandomCoordinate(nil , nil, surfacetypes) @@ -5555,7 +5631,8 @@ function OPSGROUP:RouteToMission(mission, delay) -- Hover --- - local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE + local zone=targetobject --Core.Zone#ZONE + waypointcoord=zone:GetCoordinate() elseif mission.type==AUFTRAG.Type.RELOCATECOHORT then @@ -5580,6 +5657,15 @@ 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.CAPTUREZONE then + + -- Get the zone. + targetzone=targetobject:GetZone() + + -- Random coordinate. + waypointcoord=targetzone:GetRandomCoordinate(nil , nil, surfacetypes) + else --- @@ -5706,6 +5792,8 @@ function OPSGROUP:RouteToMission(mission, delay) -- Add waypoint task. UpdateRoute is called inside. local waypointtask=self:AddTaskWaypoint(mission.DCStask, waypoint, mission.name, mission.prio, mission.duration) waypointtask.ismission=true + + waypointtask.target=targetobject -- Set waypoint task. mission:SetGroupWaypointTask(self, waypointtask) @@ -5766,7 +5854,7 @@ function OPSGROUP:RouteToMission(mission, delay) --- -- Mission Specific Settings --- - self:_SetMissionOptions(mission) + self:_SetMissionOptions(mission) end end diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index 012eb7542..e9c77e08b 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -84,6 +84,7 @@ TARGET = { -- @field #string COORDINATE Target is a COORDINATE. -- @field #string AIRBASE Target is an AIRBASE. -- @field #string ZONE Target is a ZONE object. +-- @field #string OPSZONE Target is an OPSZONE object. TARGET.ObjectType={ GROUP="Group", UNIT="Unit", @@ -92,6 +93,7 @@ TARGET.ObjectType={ COORDINATE="Coordinate", AIRBASE="Airbase", ZONE="Zone", + OPSZONE="OpsZone" } @@ -312,12 +314,18 @@ end -- * SET_SCENERY -- * SET_OPSGROUP -- * SET_ZONE +-- * SET_OPSZONE -- -- @param #TARGET self -- @param Wrapper.Positionable#POSITIONABLE Object The target UNIT, GROUP, STATIC, SCENERY, AIRBASE, COORDINATE, ZONE, SET_GROUP, SET_UNIT, SET_STATIC, SET_SCENERY, SET_ZONE function TARGET:AddObject(Object) - if Object:IsInstanceOf("SET_GROUP") or Object:IsInstanceOf("SET_UNIT") or Object:IsInstanceOf("SET_STATIC") or Object:IsInstanceOf("SET_SCENERY") or Object:IsInstanceOf("SET_OPSGROUP") then + if Object:IsInstanceOf("SET_GROUP") or + Object:IsInstanceOf("SET_UNIT") or + Object:IsInstanceOf("SET_STATIC") or + Object:IsInstanceOf("SET_SCENERY") or + Object:IsInstanceOf("SET_OPSGROUP") or + Object:IsInstanceOf("SET_OPSZONE") then --- -- Sets @@ -981,6 +989,22 @@ function TARGET:_AddObject(Object) target.Life0=1 target.Life=1 + + elseif Object:IsInstanceOf("OPSZONE") then + + + local zone=Object --Ops.OpsZone#OPSZONE + Object=zone + + target.Type=TARGET.ObjectType.OPSZONE + target.Name=zone:GetName() + + target.Coordinate=zone:GetCoordinate() + + target.N0=target.N0+1 + + target.Life0=1 + target.Life=1 else self:E(self.lid.."ERROR: Unknown object type!") @@ -1097,7 +1121,7 @@ function TARGET:GetTargetLife(Target) return 1 - elseif Target.Type==TARGET.ObjectType.ZONE then + elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then return 1 @@ -1305,6 +1329,13 @@ function TARGET:GetTargetVec3(Target, Average) local vec3=object:GetVec3() return vec3 + + elseif Target.Type==TARGET.ObjectType.OPSZONE then + + local object=Target.Object --Ops.OpsZone#OPSZONE + + local vec3=object:GetZone():GetVec3() + return vec3 end @@ -1382,7 +1413,7 @@ function TARGET:GetTargetHeading(Target) -- A coordinate has no heading. Return 0. return 0 - elseif Target.Type==TARGET.ObjectType.ZONE then + elseif Target.Type==TARGET.ObjectType.ZONE or Target.Type==TARGET.ObjectType.OPSZONE then local object=Target.Object --Core.Zone#ZONE @@ -1660,6 +1691,10 @@ function TARGET:GetTargetCategory(Target) elseif Target.Type==TARGET.ObjectType.ZONE then return TARGET.Category.ZONE + + elseif Target.Type==TARGET.ObjectType.OPSZONE then + + return TARGET.Category.OPSZONE else self:E("ERROR: unknown target category!") @@ -1669,6 +1704,71 @@ function TARGET:GetTargetCategory(Target) end +--- Get coalition of target object. If an object has no coalition (*e.g.* a coordinate) it is returned as neutral. +-- @param #TARGET self +-- @param #TARGET.Object Target Target object. +-- @return #number Coalition number. +function TARGET:GetTargetCoalition(Target) + + + -- We take neutral for objects that do not have a coalition. + local coal=coalition.side.NEUTRAL + + + if Target.Type==TARGET.ObjectType.GROUP then + + if Target.Object and Target.Object:IsAlive()~=nil then + local object=Target.Object --Wrapper.Group#GROUP + + coal=object:GetCoalition() + + end + + elseif Target.Type==TARGET.ObjectType.UNIT then + + if Target.Object and Target.Object:IsAlive()~=nil then + local object=Target.Object --Wrapper.Unit#UNIT + + coal=object:GetCoalition() + + end + + elseif Target.Type==TARGET.ObjectType.STATIC then + local object=Target.Object --Wrapper.Static#STATIC + + coal=object:GetCoalition() + + elseif Target.Type==TARGET.ObjectType.SCENERY then + + -- Scenery has no coalition. + + elseif Target.Type==TARGET.ObjectType.AIRBASE then + local object=Target.Object --Wrapper.Airbase#AIRBASE + + coal=object:GetCoalition() + + elseif Target.Type==TARGET.ObjectType.COORDINATE then + + -- Coordinate has no coalition. + + elseif Target.Type==TARGET.ObjectType.ZONE then + + -- Zone has no coalition. + + elseif Target.Type==TARGET.ObjectType.OPSZONE then + local object=Target.Object --Ops.OpsZone#OPSZONE + + coal=object:GetOwner() + + else + self:E("ERROR: unknown target category!") + end + + + return coal +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Misc Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1692,14 +1792,45 @@ end --- Get the first target objective alive. -- @param #TARGET self +-- @param Core.Point#COORDINATE RefCoordinate (Optional) Reference coordinate to determine the closest target objective. +-- @param #table Coalitions (Optional) Only consider targets of the given coalition(s). -- @return #TARGET.Object The target objective. -function TARGET:GetObjective() +function TARGET:GetObjective(RefCoordinate, Coalitions) - for _,_target in pairs(self.targets) do - local target=_target --#TARGET.Object - if target.Status~=TARGET.ObjectStatus.DEAD then - return target + if RefCoordinate then + + local dmin=math.huge + local tmin=nil --#TARGET.Object + + for _,_target in pairs(self.targets) do + local target=_target --#TARGET.Object + + if target.Status~=TARGET.ObjectStatus.DEAD and (Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions), self:GetTargetCoalition(target))) then + + local vec3=self:GetTargetVec3(target) + + local d=UTILS.VecDist3D(vec3, RefCoordinate) + + if d1 then - N=N+1 + if Coalitions==nil or UTILS.IsInTable(UTILS.EnsureTable(Coalitions), unit:GetCoalition()) then + N=N+1 + end end end @@ -1744,7 +1881,9 @@ function TARGET:CountObjectives(Target) local target=Target.Object --Wrapper.Unit#UNIT if target and target:IsAlive()~=nil and target:GetLife()>1 then - N=N+1 + if Coalitions==nil or UTILS.IsInTable(Coalitions, target:GetCoalition()) then + N=N+1 + end end elseif Target.Type==TARGET.ObjectType.STATIC then @@ -1752,7 +1891,9 @@ function TARGET:CountObjectives(Target) local target=Target.Object --Wrapper.Static#STATIC if target and target:IsAlive() then - N=N+1 + if Coalitions==nil or UTILS.IsInTable(Coalitions, target:GetCoalition()) then + N=N+1 + end end elseif Target.Type==TARGET.ObjectType.SCENERY then @@ -1763,8 +1904,12 @@ function TARGET:CountObjectives(Target) elseif Target.Type==TARGET.ObjectType.AIRBASE then + local target=Target.Object --Wrapper.Airbase#AIRBASE + if Target.Status==TARGET.ObjectStatus.ALIVE then - N=N+1 + if Coalitions==nil or UTILS.IsInTable(Coalitions, target:GetCoalition()) then + N=N+1 + end end elseif Target.Type==TARGET.ObjectType.COORDINATE then @@ -1774,7 +1919,15 @@ function TARGET:CountObjectives(Target) elseif Target.Type==TARGET.ObjectType.ZONE then -- No target we can check! - + + elseif Target.Type==TARGET.ObjectType.OPSZONE then + + local target=Target.Object --Ops.OpsZone#OPSZONE + + if Coalitions==nil or UTILS.IsInTable(Coalitions, target:GetOwner()) then + N=N+1 + end + else self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") end @@ -1784,15 +1937,16 @@ end --- Count alive targets. -- @param #TARGET self +-- @param #table Coalitions (Optional) Only count targets of the given coalition(s). -- @return #number Number of alive target objects. -function TARGET:CountTargets() +function TARGET:CountTargets(Coalitions) local N=0 for _,_target in pairs(self.targets) do local Target=_target --#TARGET.Object - N=N+self:CountObjectives(Target) + N=N+self:CountObjectives(Target, Coalitions) end diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 4bc76afac..8ae06d6b1 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1061,8 +1061,8 @@ function UTILS.Vec2Norm(a) end --- Calculate the distance between two 2D vectors. --- @param DCS#Vec2 a Vector in 3D with x, y components. --- @param DCS#Vec2 b Vector in 3D with x, y components. +-- @param DCS#Vec2 a Vector in 2D with x, y components. +-- @param DCS#Vec2 b Vector in 2D with x, y components. -- @return #number Distance between the vectors. function UTILS.VecDist2D(a, b) @@ -1446,6 +1446,30 @@ function UTILS.GetCoalitionName(Coalition) end +--- Get the enemy coalition for a given coalition. +-- @param #number Coalition The coalition ID. +-- @param #boolean Neutral Include neutral as enemy. +-- @return #table Enemy coalition table. +function UTILS.GetCoalitionEnemy(Coalition, Neutral) + + local Coalitions={} + if Coalition then + if Coalition==coalition.side.RED then + Coalitions={coalition.side.BLUE} + elseif Coalition==coalition.side.BLUE then + Coalitions={coalition.side.RED} + elseif Coalition==coalition.side.NEUTRAL then + Coalitions={coalition.side.RED, coalition.side.BLUE} + end + end + + if Neutral then + table.insert(Coalitions, coalition.side.NEUTRAL) + end + + return Coalitions +end + --- Get the modulation name from its numerical value. -- @param #number Modulation The modulation enumerator number. Can be either 0 or 1. -- @return #string The modulation name, i.e. "AM"=0 or "FM"=1. Anything else will return "Unknown".