From 58dc353bcd244e4e6dec6596214a9dc7abf442bc Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 2 Jan 2023 11:54:05 +0100 Subject: [PATCH] ZONE --- Moose Development/Moose/Core/Zone.lua | 72 ++++++++++ Moose Development/Moose/Ops/ArmyGroup.lua | 5 +- Moose Development/Moose/Ops/OpsGroup.lua | 80 ++++++----- Moose Development/Moose/Ops/OpsZone.lua | 144 +++++++++++++------- Moose Development/Moose/Utilities/Enums.lua | 10 +- 5 files changed, 224 insertions(+), 87 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 5f89fdaed..70495842d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2084,6 +2084,52 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph return self end + +--- Get the smallest circular zone encompassing all points points of the polygon zone. +-- @param #ZONE_POLYGON_BASE self +-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. +-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. +-- @return #ZONE_RADIUS The circular zone. +function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone) + + local center=self:GetVec2() + + local radius=0 + + for _,_vec2 in pairs(self._.Polygon) do + local vec2=_vec2 --DCS#Vec2 + + local r=UTILS.VecDist2D(center, vec2) + + if r>radius then + radius=r + end + + end + + local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone) + + return zone +end + + +--- Get the smallest rectangular zone encompassing all points points of the polygon zone. +-- @param #ZONE_POLYGON_BASE self +-- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. +-- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. +-- @return #ZONE_POLYGON The rectangular zone. +function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName, DoNotRegisterZone) + + local vec1, vec3=self:GetBoundingVec2() + + local vec2={x=vec1.x, y=vec3.y} + local vec4={x=vec3.x, y=vec1.y} + + local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName, {vec1, vec2, vec3, vec4}) + + return zone +end + --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -2286,6 +2332,32 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } end +--- Get the bounding 2D vectors of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return DCS#Vec2 Coordinates of western-southern-lower vertex of the box. +-- @return DCS#Vec2 Coordinates of eastern-northern-upper vertex of the box. +function ZONE_POLYGON_BASE:GetBoundingVec2() + + local x1 = self._.Polygon[1].x + local y1 = self._.Polygon[1].y + local x2 = self._.Polygon[1].x + local y2 = self._.Polygon[1].y + + for i = 2, #self._.Polygon do + self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) + x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 + x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 + y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 + y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 + + end + + local vec1={x=x1, y=y1} + local vec2={x=x2, y=y2} + + return vec1, vec2 +end + --- Draw a frontier on the F10 map with small filled circles. -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 4fba93277..edd3fc94a 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -1826,8 +1826,11 @@ function ARMYGROUP:_UpdateEngageTarget() -- Distance to last known position of target. local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) + -- Check line of sight to target. + local los=self:HasLoS(vec3) + -- 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 + if dist>100 or los==false then --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 35b71d0dd..cee9f1eca 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -1539,49 +1539,67 @@ function OPSGROUP:SetReturnOnOutOfAmmo() return self end - --- Check if an element of the group has line of sight to a coordinate. -- @param #OPSGROUP self --- @param Core.Point#COORDINATE Coordinate The position to which we check the LoS. +-- @param Core.Point#COORDINATE Coordinate The position to which we check the LoS. Can also be a DCS#Vec3. -- @param #OPSGROUP.Element Element The (optinal) element. If not given, all elements are checked. -- @param DCS#Vec3 OffsetElement Offset vector of the element. -- @param DCS#Vec3 OffsetCoordinate Offset vector of the coordinate. -- @return #boolean If `true`, there is line of sight to the specified coordinate. function OPSGROUP:HasLoS(Coordinate, Element, OffsetElement, OffsetCoordinate) - -- Target vector. - local Vec3=Coordinate:GetVec3() + if Coordinate then - -- Optional offset. - if OffsetCoordinate then - Vec3=UTILS.VecAdd(Vec3, OffsetCoordinate) - end - - --- Function to check LoS for an element of the group. - local function checklos(element) - local vec3=element.unit:GetVec3() - if OffsetElement then - vec3=UTILS.VecAdd(vec3, OffsetElement) + -- Target vector. + local Vec3={x=Coordinate.x, y=Coordinate.y, z=Coordinate.z} --Coordinate:GetVec3() + + -- Optional offset. + if OffsetCoordinate then + Vec3=UTILS.VecAdd(Vec3, OffsetCoordinate) end - local _los=land.isVisible(vec3, Vec3) - --self:I({los=_los, source=vec3, target=Vec3}) - return _los - end - - if Element then - local los=checklos(Element) - return los - else - - for _,element in pairs(self.elements) do - -- Get LoS of this element. - local los=checklos(element) - if los then - return true + + --- Function to check LoS for an element of the group. + local function checklos(vec3) + if vec3 then + if OffsetElement then + vec3=UTILS.VecAdd(vec3, OffsetElement) + end + local _los=land.isVisible(vec3, Vec3) + --self:I({los=_los, source=vec3, target=Vec3}) + return _los + end + return nil + end + + if Element then + -- Check los for the given element. + if Element.unit and Element.unit:IsAlive() then + local vec3=Element.unit:GetVec3() + local los=checklos(Element) + return los + end + else + + -- Check if any element has los. + local gotit=false + for _,_element in pairs(self.elements) do + local element=_element --#OPSGROUP.Element + if element and element.unit and element.unit:IsAlive() then + gotit=true + local vec3=element.unit:GetVec3() + -- Get LoS of this element. + local los=checklos(vec3) + if los then + return true + end + end + end + + if gotit then + return false end end - - return false + end return nil diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 831f2a1b8..92d7d2c9b 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -5,6 +5,7 @@ -- * Monitor if a zone is captured -- * Monitor if an airbase is captured -- * Define conditions under which zones are captured/held +-- * Supports circular and polygon zone shapes -- -- === -- @@ -20,6 +21,7 @@ -- @field #string lid DCS log ID string. -- @field #number verbose Verbosity of output. -- @field Core.Zone#ZONE zone The zone. +-- @field Core.Zone#ZONE_RADIUS zoneCircular The circular zone. -- @field Wrapper.Airbase#AIRBASE airbase The airbase that is monitored. -- @field #string airbaseName Name of the airbase that is monitored. -- @field #string zoneName Name of the zone. @@ -60,9 +62,6 @@ -- -- An OPSZONE is a strategically important area. -- --- **Restrictions** --- --- * Since we are using a DCS routine that scans a zone for units or other objects present in the zone and this DCS routine is limited to cicular zones, only those can be used. -- -- @field #OPSZONE OPSZONE = { @@ -84,9 +83,19 @@ OPSZONE = { -- @field #string Type Type of mission -- @field Ops.Auftrag#AUFTRAG Mission The actual attached mission + +--- Type of zone we are dealing with. +-- @type OPSZONE.ZoneType +-- @field #string Circular Zone is circular. +-- @field #string Polygon Zone is a polygon. +OPSZONE.ZoneType={ + Circular="Circular", + Polygon="Polygon", +} + --- OPSZONE class version. -- @field #string version -OPSZONE.version="0.4.0" +OPSZONE.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -94,6 +103,7 @@ OPSZONE.version="0.4.0" -- TODO: Pause/unpause evaluations. -- TODO: Differentiate between ground attack and boming by air or arty. +-- DONE: Polygon zones. -- DONE: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it. -- DONE: Capturing based on (total) threat level threshold. Unarmed units do not pose a threat and should not be able to hold a zone. -- DONE: Can neutrals capture? No, since they are _neutral_! @@ -125,7 +135,7 @@ function OPSZONE:New(Zone, CoalitionOwner) if type(Zone)=="string" then -- Convert string into a ZONE or ZONE_AIRBASE local Name=Zone - Zone=ZONE:New(Name) + Zone=ZONE:FindByName(Name) if not Zone then local airbase=AIRBASE:FindByName(Name) if airbase then @@ -146,8 +156,17 @@ function OPSZONE:New(Zone, CoalitionOwner) if Zone:IsInstanceOf("ZONE_AIRBASE") then self.airbase=Zone._.ZoneAirbase self.airbaseName=self.airbase:GetName() + self.zoneType=OPSZONE.ZoneType.Circular + self.zoneCircular=Zone elseif Zone:IsInstanceOf("ZONE_RADIUS") then -- Nothing to do. + self.zoneType=OPSZONE.ZoneType.Circular + self.zoneCircular=Zone + elseif Zone:IsInstanceOf("ZONE_POLYGON_BASE") then + -- Nothing to do. + self.zoneType=OPSZONE.ZoneType.Polygon + local zone=Zone --Core.Zone#ZONE_POLYGON + self.zoneCircular=zone:GetZoneRadius(nil, true) else self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") return nil @@ -156,10 +175,10 @@ function OPSZONE:New(Zone, CoalitionOwner) -- Set some string id for output to DCS.log file. self.lid=string.format("OPSZONE %s | ", Zone:GetName()) - -- Set some values. + -- Set some values. self.zone=Zone self.zoneName=Zone:GetName() - self.zoneRadius=Zone:GetRadius() + self.zoneRadius=self.zoneCircular:GetRadius() self.Missions = {} self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone}) self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone}) @@ -820,8 +839,6 @@ function OPSZONE:onafterEmpty(From, Event, To) -- Debug info. self:T(self.lid..string.format("Zone is empty EVENT")) - - end --- On after "Attacked" event. @@ -1034,42 +1051,55 @@ function OPSZONE:Scan() local tl=0 local unit=UNIT:Find(DCSUnit) if unit then - - -- Threat level of unit. - tl=unit:GetThreatLevel() + + -- Inside zone. + local inzone=true + if self.zoneType==OPSZONE.ZoneType.Polygon then - -- Add unit to set. - self.ScanUnitSet:AddUnit(unit) + -- Check if unit is really inside the zone. + inzone=unit:IsInZone(self.zone) + + -- Debug marker. + -- Debug: Had cases where a (red) unit was clearly not inside the zone but the scan did find it! + unit:GetCoordinate():MarkToAll(string.format("Unit %s inzone=%s", unit:GetName(), tostring(inzone))) + end - -- Debug: Had cases where a (red) unit was clearly not inside the zone but the scan did find it! - --local inzone=unit:IsInZone(self.zone) - --unit:GetCoordinate():MarkToAll(string.format("Unit %s inzone=%s", unit:GetName(), tostring(inzone))) + if inzone then + + -- Threat level of unit. + tl=unit:GetThreatLevel() + + -- Add unit to set. + self.ScanUnitSet:AddUnit(unit) + + -- Get group of unit. + local group=unit:GetGroup() - -- Get group of unit. - local group=unit:GetGroup() + -- Add group to scanned set. + if group then + self.ScanGroupSet:AddGroup(group, true) + end + + -- Increase counter. + if Coalition==coalition.side.RED then + Nred=Nred+1 + Tred=Tred+tl + elseif Coalition==coalition.side.BLUE then + Nblu=Nblu+1 + Tblu=Tblu+tl + elseif Coalition==coalition.side.NEUTRAL then + Nnut=Nnut+1 + Tnut=Tnut+tl + end + + -- Debug info. + if self.verbose>=4 then + self:I(self.lid..string.format("Found unit %s (coalition=%d)", DCSUnit:getName(), Coalition)) + end - if group then - self.ScanGroupSet:AddGroup(group, true) end end - - -- Increase counter. - if Coalition==coalition.side.RED then - Nred=Nred+1 - Tred=Tred+tl - elseif Coalition==coalition.side.BLUE then - Nblu=Nblu+1 - Tblu=Tblu+tl - elseif Coalition==coalition.side.NEUTRAL then - Nnut=Nnut+1 - Tnut=Tnut+tl - end - - -- Debug info. - if self.verbose>=4 then - self:I(self.lid..string.format("Found unit %s (coalition=%d)", DCSUnit:getName(), Coalition)) - end end elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then @@ -1079,26 +1109,40 @@ function OPSZONE:Scan() --- -- This is a DCS static object. - local DCSStatic=ZoneObject --DCS#Static + local DCSStatic=ZoneObject --DCS#StaticObject -- Get coalition. local Coalition=DCSStatic:getCoalition() -- CAREFUL! Downed pilots break routine here without any error thrown. --local unit=STATIC:Find(DCSStatic) - - -- Increase counter. - if Coalition==coalition.side.RED then - Nred=Nred+1 - elseif Coalition==coalition.side.BLUE then - Nblu=Nblu+1 - elseif Coalition==coalition.side.NEUTRAL then - Nnut=Nnut+1 + + -- Inside zone. + local inzone=true + if self.zoneType==OPSZONE.ZoneType.Polygon then + + local Vec3=DCSStatic:getPoint() + + inzone=self.zone:IsVec3InZone(Vec3) + end - -- Debug info - if self.verbose>=4 then - self:I(self.lid..string.format("Found static %s (coalition=%d)", DCSStatic:getName(), Coalition)) + if inzone then + + -- Increase counter. + if Coalition==coalition.side.RED then + Nred=Nred+1 + elseif Coalition==coalition.side.BLUE then + Nblu=Nblu+1 + elseif Coalition==coalition.side.NEUTRAL then + Nnut=Nnut+1 + end + + -- Debug info + if self.verbose>=4 then + self:I(self.lid..string.format("Found static %s (coalition=%d)", DCSStatic:getName(), Coalition)) + end + end elseif ObjectCategory==Object.Category.SCENERY then diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 63f3d6187..678510025 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -28,11 +28,11 @@ ENUMS = {} --- Rules of Engagement. -- @type ENUMS.ROE --- @field #number WeaponFree AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target. --- @field #number OpenFireWeaponFree AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking. --- @field #number OpenFire AI will engage only targets specified in its taskings. --- @field #number ReturnFire AI will only engage threats that shoot first. --- @field #number WeaponHold AI will hold fire under all circumstances. +-- @field #number WeaponFree [AIR] AI will engage any enemy group it detects. Target prioritization is based based on the threat of the target. +-- @field #number OpenFireWeaponFree [AIR] AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking. +-- @field #number OpenFire [AIR, GROUND, NAVAL] AI will engage only targets specified in its taskings. +-- @field #number ReturnFire [AIR, GROUND, NAVAL] AI will only engage threats that shoot first. +-- @field #number WeaponHold [AIR, GROUND, NAVAL] AI will hold fire under all circumstances. ENUMS.ROE = { WeaponFree=0, OpenFireWeaponFree=1,