This commit is contained in:
Frank 2023-01-02 11:54:05 +01:00
parent b194985827
commit 58dc353bcd
5 changed files with 224 additions and 87 deletions

View File

@ -2084,6 +2084,52 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph
return self return self
end 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. --- Smokes the zone boundaries in a color.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @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 } return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 }
end 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. --- Draw a frontier on the F10 map with small filled circles.
-- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE self
-- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. -- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All.

View File

@ -1826,8 +1826,11 @@ function ARMYGROUP:_UpdateEngageTarget()
-- Distance to last known position of target. -- Distance to last known position of target.
local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) 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. -- 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()) --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName())

View File

@ -1539,18 +1539,19 @@ function OPSGROUP:SetReturnOnOutOfAmmo()
return self return self
end end
--- Check if an element of the group has line of sight to a coordinate. --- Check if an element of the group has line of sight to a coordinate.
-- @param #OPSGROUP self -- @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 #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 OffsetElement Offset vector of the element.
-- @param DCS#Vec3 OffsetCoordinate Offset vector of the coordinate. -- @param DCS#Vec3 OffsetCoordinate Offset vector of the coordinate.
-- @return #boolean If `true`, there is line of sight to the specified coordinate. -- @return #boolean If `true`, there is line of sight to the specified coordinate.
function OPSGROUP:HasLoS(Coordinate, Element, OffsetElement, OffsetCoordinate) function OPSGROUP:HasLoS(Coordinate, Element, OffsetElement, OffsetCoordinate)
if Coordinate then
-- Target vector. -- Target vector.
local Vec3=Coordinate:GetVec3() local Vec3={x=Coordinate.x, y=Coordinate.y, z=Coordinate.z} --Coordinate:GetVec3()
-- Optional offset. -- Optional offset.
if OffsetCoordinate then if OffsetCoordinate then
@ -1558,8 +1559,8 @@ function OPSGROUP:HasLoS(Coordinate, Element, OffsetElement, OffsetCoordinate)
end end
--- Function to check LoS for an element of the group. --- Function to check LoS for an element of the group.
local function checklos(element) local function checklos(vec3)
local vec3=element.unit:GetVec3() if vec3 then
if OffsetElement then if OffsetElement then
vec3=UTILS.VecAdd(vec3, OffsetElement) vec3=UTILS.VecAdd(vec3, OffsetElement)
end end
@ -1567,22 +1568,39 @@ function OPSGROUP:HasLoS(Coordinate, Element, OffsetElement, OffsetCoordinate)
--self:I({los=_los, source=vec3, target=Vec3}) --self:I({los=_los, source=vec3, target=Vec3})
return _los return _los
end end
return nil
end
if Element then 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) local los=checklos(Element)
return los return los
end
else else
for _,element in pairs(self.elements) do -- 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. -- Get LoS of this element.
local los=checklos(element) local los=checklos(vec3)
if los then if los then
return true return true
end end
end end
end
if gotit then
return false return false
end end
end
end
return nil return nil
end end

View File

@ -5,6 +5,7 @@
-- * Monitor if a zone is captured -- * Monitor if a zone is captured
-- * Monitor if an airbase is captured -- * Monitor if an airbase is captured
-- * Define conditions under which zones are captured/held -- * 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 #string lid DCS log ID string.
-- @field #number verbose Verbosity of output. -- @field #number verbose Verbosity of output.
-- @field Core.Zone#ZONE zone The zone. -- @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 Wrapper.Airbase#AIRBASE airbase The airbase that is monitored.
-- @field #string airbaseName Name of the airbase that is monitored. -- @field #string airbaseName Name of the airbase that is monitored.
-- @field #string zoneName Name of the zone. -- @field #string zoneName Name of the zone.
@ -60,9 +62,6 @@
-- --
-- An OPSZONE is a strategically important area. -- 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 -- @field #OPSZONE
OPSZONE = { OPSZONE = {
@ -84,9 +83,19 @@ OPSZONE = {
-- @field #string Type Type of mission -- @field #string Type Type of mission
-- @field Ops.Auftrag#AUFTRAG Mission The actual attached 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. --- OPSZONE class version.
-- @field #string version -- @field #string version
OPSZONE.version="0.4.0" OPSZONE.version="0.5.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@ -94,6 +103,7 @@ OPSZONE.version="0.4.0"
-- TODO: Pause/unpause evaluations. -- TODO: Pause/unpause evaluations.
-- TODO: Differentiate between ground attack and boming by air or arty. -- 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: 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: 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_! -- DONE: Can neutrals capture? No, since they are _neutral_!
@ -125,7 +135,7 @@ function OPSZONE:New(Zone, CoalitionOwner)
if type(Zone)=="string" then if type(Zone)=="string" then
-- Convert string into a ZONE or ZONE_AIRBASE -- Convert string into a ZONE or ZONE_AIRBASE
local Name=Zone local Name=Zone
Zone=ZONE:New(Name) Zone=ZONE:FindByName(Name)
if not Zone then if not Zone then
local airbase=AIRBASE:FindByName(Name) local airbase=AIRBASE:FindByName(Name)
if airbase then if airbase then
@ -146,8 +156,17 @@ function OPSZONE:New(Zone, CoalitionOwner)
if Zone:IsInstanceOf("ZONE_AIRBASE") then if Zone:IsInstanceOf("ZONE_AIRBASE") then
self.airbase=Zone._.ZoneAirbase self.airbase=Zone._.ZoneAirbase
self.airbaseName=self.airbase:GetName() self.airbaseName=self.airbase:GetName()
self.zoneType=OPSZONE.ZoneType.Circular
self.zoneCircular=Zone
elseif Zone:IsInstanceOf("ZONE_RADIUS") then elseif Zone:IsInstanceOf("ZONE_RADIUS") then
-- Nothing to do. -- 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 else
self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!") self:E("ERROR: OPSZONE must be a SPHERICAL zone due to DCS restrictions!")
return nil return nil
@ -159,7 +178,7 @@ function OPSZONE:New(Zone, CoalitionOwner)
-- Set some values. -- Set some values.
self.zone=Zone self.zone=Zone
self.zoneName=Zone:GetName() self.zoneName=Zone:GetName()
self.zoneRadius=Zone:GetRadius() self.zoneRadius=self.zoneCircular:GetRadius()
self.Missions = {} self.Missions = {}
self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone}) self.ScanUnitSet=SET_UNIT:New():FilterZones({Zone})
self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone}) self.ScanGroupSet=SET_GROUP:New():FilterZones({Zone})
@ -820,8 +839,6 @@ function OPSZONE:onafterEmpty(From, Event, To)
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Zone is empty EVENT")) self:T(self.lid..string.format("Zone is empty EVENT"))
end end
--- On after "Attacked" event. --- On after "Attacked" event.
@ -1035,24 +1052,33 @@ function OPSZONE:Scan()
local unit=UNIT:Find(DCSUnit) local unit=UNIT:Find(DCSUnit)
if unit then if unit then
-- Inside zone.
local inzone=true
if self.zoneType==OPSZONE.ZoneType.Polygon then
-- 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
if inzone then
-- Threat level of unit. -- Threat level of unit.
tl=unit:GetThreatLevel() tl=unit:GetThreatLevel()
-- Add unit to set. -- Add unit to set.
self.ScanUnitSet:AddUnit(unit) self.ScanUnitSet:AddUnit(unit)
-- 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)))
-- Get group of unit. -- Get group of unit.
local group=unit:GetGroup() local group=unit:GetGroup()
-- Add group to scanned set.
if group then if group then
self.ScanGroupSet:AddGroup(group, true) self.ScanGroupSet:AddGroup(group, true)
end end
end
-- Increase counter. -- Increase counter.
if Coalition==coalition.side.RED then if Coalition==coalition.side.RED then
@ -1070,6 +1096,10 @@ function OPSZONE:Scan()
if self.verbose>=4 then if self.verbose>=4 then
self:I(self.lid..string.format("Found unit %s (coalition=%d)", DCSUnit:getName(), Coalition)) self:I(self.lid..string.format("Found unit %s (coalition=%d)", DCSUnit:getName(), Coalition))
end end
end
end
end end
elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then elseif ObjectCategory==Object.Category.STATIC and ZoneObject:isExist() then
@ -1079,7 +1109,7 @@ function OPSZONE:Scan()
--- ---
-- This is a DCS static object. -- This is a DCS static object.
local DCSStatic=ZoneObject --DCS#Static local DCSStatic=ZoneObject --DCS#StaticObject
-- Get coalition. -- Get coalition.
local Coalition=DCSStatic:getCoalition() local Coalition=DCSStatic:getCoalition()
@ -1087,6 +1117,18 @@ function OPSZONE:Scan()
-- CAREFUL! Downed pilots break routine here without any error thrown. -- CAREFUL! Downed pilots break routine here without any error thrown.
--local unit=STATIC:Find(DCSStatic) --local unit=STATIC:Find(DCSStatic)
-- Inside zone.
local inzone=true
if self.zoneType==OPSZONE.ZoneType.Polygon then
local Vec3=DCSStatic:getPoint()
inzone=self.zone:IsVec3InZone(Vec3)
end
if inzone then
-- Increase counter. -- Increase counter.
if Coalition==coalition.side.RED then if Coalition==coalition.side.RED then
Nred=Nred+1 Nred=Nred+1
@ -1101,6 +1143,8 @@ function OPSZONE:Scan()
self:I(self.lid..string.format("Found static %s (coalition=%d)", DCSStatic:getName(), Coalition)) self:I(self.lid..string.format("Found static %s (coalition=%d)", DCSStatic:getName(), Coalition))
end end
end
elseif ObjectCategory==Object.Category.SCENERY then elseif ObjectCategory==Object.Category.SCENERY then
--- ---

View File

@ -28,11 +28,11 @@ ENUMS = {}
--- Rules of Engagement. --- Rules of Engagement.
-- @type ENUMS.ROE -- @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 WeaponFree [AIR] 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 OpenFireWeaponFree [AIR] 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 OpenFire [AIR, GROUND, NAVAL] AI will engage only targets specified in its taskings.
-- @field #number ReturnFire AI will only engage threats that shoot first. -- @field #number ReturnFire [AIR, GROUND, NAVAL] AI will only engage threats that shoot first.
-- @field #number WeaponHold AI will hold fire under all circumstances. -- @field #number WeaponHold [AIR, GROUND, NAVAL] AI will hold fire under all circumstances.
ENUMS.ROE = { ENUMS.ROE = {
WeaponFree=0, WeaponFree=0,
OpenFireWeaponFree=1, OpenFireWeaponFree=1,