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/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cc82caa61..e02ba3008 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -7987,120 +7987,123 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Loop over all assets that need a parking psot. for _,asset in pairs(assets) do local _asset=asset --#WAREHOUSE.Assetitem - - -- Get terminal type of this asset - local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory()) - - -- Asset specific parking. - parking[_asset.uid]={} - - -- Loop over all units - each one needs a spot. - for i=1,_asset.nunits do - -- Asset name - local assetname=_asset.spawngroupname.."-"..tostring(i) + if not _asset.spawned then - -- Loop over all parking spots. - local gotit=false - for _,_parkingspot in pairs(parkingdata) do - local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot - - -- Parking valid? - local valid=true - - if asset.parkingIDs then - -- If asset has assigned parking spots, we take these no matter what. - valid=self:_CheckParkingAsset(parkingspot, asset) - else - - -- Valid terminal type depending on attribute. - local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) + -- Get terminal type of this asset + local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory()) + + -- Asset specific parking. + parking[_asset.uid]={} + + -- Loop over all units - each one needs a spot. + for i=1,_asset.nunits do + + -- Asset name + local assetname=_asset.spawngroupname.."-"..tostring(i) + + -- Loop over all parking spots. + local gotit=false + for _,_parkingspot in pairs(parkingdata) do + local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot - -- Valid parking list. - local validParking=self:_CheckParkingValid(parkingspot) + -- Parking valid? + local valid=true - -- Black and white list. - local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID) - - -- Debug info. - --env.info(string.format("FF validTerminal = %s", tostring(validTerminal))) - --env.info(string.format("FF validParking = %s", tostring(validParking))) - --env.info(string.format("FF validBWlist = %s", tostring(validBWlist))) - - -- Check if all are true - valid=validTerminal and validParking and validBWlist - end - - - -- Check correct terminal type for asset. We don't want helos in shelters etc. - if valid then - - -- Coordinate of the parking spot. - local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE - local _termid=parkingspot.TerminalID - local free=true - local problem=nil - - -- Loop over all obstacles. - for _,obstacle in pairs(obstacles) do - - -- Check if aircraft overlaps with any obstacle. - local dist=_spot:Get2DDistance(obstacle.coord) - local safe=_overlap(_asset.size, obstacle.size, dist) - - -- Spot is blocked. - if not safe then - self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist)) - free=false - problem=obstacle - problem.dist=dist - break - else - --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist)) - end - - end - - -- Check if spot is free - if free then - - -- Add parkingspot for this asset unit. - table.insert(parking[_asset.uid], parkingspot) - - -- Debug - self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid)) - - -- Add the unit as obstacle so that this spot will not be available for the next unit. - table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"}) - - gotit=true - break - + if asset.parkingIDs then + -- If asset has assigned parking spots, we take these no matter what. + valid=self:_CheckParkingAsset(parkingspot, asset) else - - -- Debug output for occupied spots. - if self.Debug then - local coord=problem.coord --Core.Point#COORDINATE - local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist) - self:I(self.lid..text) - coord:MarkToAll(string.format(text)) - else - self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) - end - + + -- Valid terminal type depending on attribute. + local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) + + -- Valid parking list. + local validParking=self:_CheckParkingValid(parkingspot) + + -- Black and white list. + local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID) + + -- Debug info. + --env.info(string.format("FF validTerminal = %s", tostring(validTerminal))) + --env.info(string.format("FF validParking = %s", tostring(validParking))) + --env.info(string.format("FF validBWlist = %s", tostring(validBWlist))) + + -- Check if all are true + valid=validTerminal and validParking and validBWlist end - - else - self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType)) - end -- check terminal type - end -- loop over parking spots - - -- No parking spot for at least one asset :( - if not gotit then - self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid)) - return nil - end - end -- loop over asset units + + + -- Check correct terminal type for asset. We don't want helos in shelters etc. + if valid then + + -- Coordinate of the parking spot. + local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE + local _termid=parkingspot.TerminalID + local free=true + local problem=nil + + -- Loop over all obstacles. + for _,obstacle in pairs(obstacles) do + + -- Check if aircraft overlaps with any obstacle. + local dist=_spot:Get2DDistance(obstacle.coord) + local safe=_overlap(_asset.size, obstacle.size, dist) + + -- Spot is blocked. + if not safe then + self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist)) + free=false + problem=obstacle + problem.dist=dist + break + else + --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist)) + end + + end + + -- Check if spot is free + if free then + + -- Add parkingspot for this asset unit. + table.insert(parking[_asset.uid], parkingspot) + + -- Debug + self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid)) + + -- Add the unit as obstacle so that this spot will not be available for the next unit. + table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"}) + + gotit=true + break + + else + + -- Debug output for occupied spots. + if self.Debug then + local coord=problem.coord --Core.Point#COORDINATE + local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist) + self:I(self.lid..text) + coord:MarkToAll(string.format(text)) + else + self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) + end + + end + + else + self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType)) + end -- check terminal type + end -- loop over parking spots + + -- No parking spot for at least one asset :( + if not gotit then + self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid)) + return nil + end + end -- loop over asset units + end -- Asset spawned check end -- loop over asset groups return parking 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/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 9347b1997..00413998e 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -5467,16 +5467,73 @@ function AUFTRAG:_SetLogID() end ---- Update DCS task. +--- Get request ID from legion this mission requested assets from -- @param #AUFTRAG self --- @return #AUFTRAG self -function AUFTRAG:_UpdateTask() +-- @param Ops.Legion#LEGION Legion The legion from which to get the request ID. +-- @return #number Request ID (if any). +function AUFTRAG:_GetRequestID(Legion) + local requestid=nil + local name=nil + if type(Legion)=="string" then + name=Legion + else + name=Legion.alias + end + + if name then + requestid=self.requestID[name] + end + + return nil +end + + +--- Get request from legion this mission requested assets from. +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion from which to get the request ID. +-- @return Functional.Warehouse#WAREHOUSE.PendingItem Request. +function AUFTRAG:_GetRequest(Legion) + + local request=nil + + local requestID=self:_GetRequestID(Legion) + + if requestID then + request=Legion:GetRequestByID(requestID) + end + + return request +end + +--- Set request ID from legion this mission requested assets from +-- @param #AUFTRAG self +-- @param Ops.Legion#LEGION Legion The legion from which to get the request ID. +-- @param #number RequestID Request ID. +-- @return #AUFTRAG self +function AUFTRAG:_SetRequestID(Legion, RequestID) + + local requestid=nil + local name=nil + + if type(Legion)=="string" then + name=Legion + else + name=Legion.alias + end + + if name then + if self.requestID[name] then + self:I(self.lid..string.format("WARNING: Mission already has a request ID=%d!", self.requestID[name])) + end + self.requestID[name]=RequestID + end return self end + --- Update mission F10 map marker. -- @param #AUFTRAG self -- @return #AUFTRAG self diff --git a/Moose Development/Moose/Ops/Brigade.lua b/Moose Development/Moose/Ops/Brigade.lua index 3ce1e6be1..73ae5e043 100644 --- a/Moose Development/Moose/Ops/Brigade.lua +++ b/Moose Development/Moose/Ops/Brigade.lua @@ -572,9 +572,9 @@ function BRIGADE:onafterStatus(From, Event, To) self:I(self.lid..text) end - ------------------- + --------------------- -- Refuelling Info -- - ------------------- + --------------------- if self.verbose>=4 then local text="Refuelling Zones:" for i,_refuellingzone in pairs(self.refuellingZones) do @@ -583,7 +583,20 @@ function BRIGADE:onafterStatus(From, Event, To) text=text..string.format("\n* %s: Mission status=%s, suppliers=%d", refuellingzone.zone:GetName(), refuellingzone.mission:GetState(), refuellingzone.mission:CountOpsGroups()) end self:I(self.lid..text) - end + end + + ---------------- + -- Asset Info -- + ---------------- + if self.verbose>=5 then + local text="Assets in stock:" + for i,_asset in pairs(self.stock) do + local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem + -- Info text. + text=text..string.format("\n* %s: spawned=%s", asset.spawngroupname, tostring(asset.spawned)) + end + self:I(self.lid..text) + end end diff --git a/Moose Development/Moose/Ops/Legion.lua b/Moose Development/Moose/Ops/Legion.lua index df441a2ec..141c74c31 100644 --- a/Moose Development/Moose/Ops/Legion.lua +++ b/Moose Development/Moose/Ops/Legion.lua @@ -839,6 +839,58 @@ function LEGION:onafterMissionAssign(From, Event, To, Mission, Legions) end +--- Create a request and add it to the warehouse queue. +-- @param #LEGION self +-- @param Functional.Warehouse#WAREHOUSE.Descriptor AssetDescriptor Descriptor describing the asset that is requested. +-- @param AssetDescriptorValue Value of the asset descriptor. Type depends on descriptor, i.e. could be a string, etc. +-- @param #number nAsset Number of groups requested that match the asset specification. +-- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. +-- @param #string Assignment A keyword or text that can later be used to identify this request and postprocess the assets. +function LEGION:_AddRequest(AssetDescriptor, AssetDescriptorValue, nAsset, Prio, Assignment) + + -- Defaults. + nAsset=nAsset or 1 + Prio=Prio or 50 + + -- Increase id. + self.queueid=self.queueid+1 + + -- Request queue table item. + local request={ + uid=self.queueid, + prio=Prio, + warehouse=self, + assetdesc=AssetDescriptor, + assetdescval=AssetDescriptorValue, + nasset=nAsset, + transporttype=WAREHOUSE.TransportType.SELFPROPELLED, + ntransport=0, + assignment=tostring(Assignment), + airbase=self:GetAirbase(), + category=self:GetAirbaseCategory(), + ndelivered=0, + ntransporthome=0, + assets={}, + toself=true, + } --Functional.Warehouse#WAREHOUSE.Queueitem + + + -- Add request to queue. + table.insert(self.queue, request) + + local descval="assetlist" + if request.assetdesc==WAREHOUSE.Descriptor.ASSETLIST then + + else + descval=tostring(request.assetdescval) + end + + local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports=%s.", + self.alias, self.alias, request.assetdesc, descval, tostring(request.nasset), request.transporttype, tostring(request.ntransport)) + self:_DebugMessage(text, 5) + +end + --- On after "MissionRequest" event. Performs a self request to the warehouse for the mission assets. Sets mission status to REQUESTED. -- @param #LEGION self @@ -914,11 +966,8 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) if Mission.type==AUFTRAG.Type.RELOCATECOHORT then cancel=true - -- Get request ID. - local requestID=currM.requestID[self.alias] - -- Get request. - local request=self:GetRequestByID(requestID) + local request=currM:_GetRequest(self) if request then self:T2(self.lid.."Removing group from cargoset") @@ -999,7 +1048,8 @@ function LEGION:onafterMissionRequest(From, Event, To, Mission) local assignment=string.format("Mission-%d", Mission.auftragsnummer) -- Add request to legion warehouse. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, assignment) + --self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, nil, nil, Mission.prio, assignment) + self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST, Assetlist, #Assetlist, Mission.prio, assignment) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. Mission.requestID[self.alias]=self.queueid @@ -1091,7 +1141,8 @@ function LEGION:onafterTransportRequest(From, Event, To, OpsTransport) local assignment=string.format("Transport-%d", OpsTransport.uid) -- Add request to legion warehouse. - self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, AssetList, #AssetList, nil, nil, OpsTransport.prio, assignment) + --self:AddRequest(self, WAREHOUSE.Descriptor.ASSETLIST, AssetList, #AssetList, nil, nil, OpsTransport.prio, assignment) + self:_AddRequest(WAREHOUSE.Descriptor.ASSETLIST, AssetList, #AssetList, OpsTransport.prio, assignment) -- The queueid has been increased in the onafterAddRequest function. So we can simply use it here. OpsTransport.requestID[self.alias]=self.queueid @@ -1198,8 +1249,9 @@ function LEGION:onafterMissionCancel(From, Event, To, Mission) end -- Remove queued request (if any). - if Mission.requestID[self.alias] then - self:_DeleteQueueItemByID(Mission.requestID[self.alias], self.queue) + local requestID=Mission:_GetRequestID(self) + if requestID then + self:_DeleteQueueItemByID(requestID, self.queue) end end 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,