From 47d814e409996fbbbe4d5f3719c452cfd206f8f2 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 21 May 2021 12:35:32 +0200 Subject: [PATCH 1/6] CONTROLLABLE:PatrolRouteRandom fix for Subs (#1536) * CONTROLLABLE:PatrolRouteRandom fix for Subs fixes issue #1535 * Update Controllable.lua * Update Controllable.lua --- .../Moose/Wrapper/Controllable.lua | 165 +++++++++++------- 1 file changed, 105 insertions(+), 60 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index e2d3edca6..58e1a747e 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -175,11 +175,8 @@ -- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat} -- -- ## 5.5) Air-2-Air missile attack range: --- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets. --- --- ## 5.6) GROUND units attack range: --- * @{#CONTROLLABLE.OptionEngageRange}(): Engage range limit in percent (a number between 0 and 100). Default 100. Defines the range at which a GROUND unit/group (e.g. a SAM site) is allowed to use its weapons automatically. --- +-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets . +-- -- @field #CONTROLLABLE CONTROLLABLE = { ClassName = "CONTROLLABLE", @@ -507,7 +504,7 @@ function CONTROLLABLE:TaskCombo( DCSTasks ) tasks = DCSTasks } } - + return DCSTaskCombo end @@ -805,12 +802,12 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:CommandSetFrequency(Frequency, Modulation, Delay) - local CommandSetFrequency = { - id = 'SetFrequency', - params = { - frequency = Frequency*1000000, - modulation = Modulation or radio.modulation.AM, - } + local CommandSetFrequency = { + id = 'SetFrequency', + params = { + frequency = Frequency*1000000, + modulation = Modulation or radio.modulation.AM, + } } if Delay and Delay>0 then @@ -884,7 +881,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At groupId = AttackGroup:GetID(), weaponType = WeaponType or 1073741822, expend = WeaponExpend or "Auto", - attackQtyLimit = AttackQty and true or false, + attackQtyLimit = AttackQty and true or false, attackQty = AttackQty or 1, directionEnabled = Direction and true or false, direction = Direction and math.rad(Direction) or 0, @@ -924,7 +921,7 @@ function CONTROLLABLE:TaskAttackUnit(AttackUnit, GroupAttack, WeaponExpend, Atta weaponType = WeaponType or 1073741822, } } - + return DCSTask end @@ -1088,7 +1085,7 @@ function CONTROLLABLE:TaskEmbarking(Coordinate, GroupSetForEmbarking, Duration, -- Distribution --local distribution={} --distribution[id]=gids - + local groupID=self and self:GetID() local DCSTask = { @@ -1317,7 +1314,7 @@ function CONTROLLABLE:TaskLandAtVec2(Vec2, Duration) duration = Duration, }, } - + return DCSTask end @@ -1432,7 +1429,7 @@ end -- @param #number AmmoCount (optional) Quantity of ammunition to expand (omit to fire until ammunition is depleted). -- @param #number WeaponType (optional) Enum for weapon type ID. This value is only required if you want the group firing to use a specific weapon, for instance using the task on a ship to force it to fire guided missiles at targets within cannon range. See http://wiki.hoggit.us/view/DCS_enum_weapon_flag -- @param #number Altitude (Optional) Altitude in meters. --- @param #number ASL Altitude is above mean sea level. Default is above ground level. +-- @param #number ASL Altitude is above mean sea level. Default is above ground level. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Altitude, ASL ) @@ -1454,7 +1451,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti DCSTask.params.expendQty = AmmoCount DCSTask.params.expendQtyEnabled = true end - + if Altitude then DCSTask.params.altitude=Altitude end @@ -1462,7 +1459,7 @@ function CONTROLLABLE:TaskFireAtPoint( Vec2, Radius, AmmoCount, WeaponType, Alti if WeaponType then DCSTask.params.weaponType=WeaponType end - + --self:I(DCSTask) return DCSTask @@ -1482,6 +1479,7 @@ end --- (AIR + GROUND) The task makes the controllable/unit a FAC and orders the FAC to control the target (enemy ground controllable) destruction. -- The killer is player-controlled allied CAS-aircraft that is in contact with the FAC. -- If the task is assigned to the controllable lead unit will be a FAC. +-- It's important to note that depending on the type of unit that is being assigned the task (AIR or GROUND), you must choose the correct type of callsign enumerator. For airborne controllables use CALLSIGN.Aircraft and for ground based use CALLSIGN.JTAC enumerators. -- @param #CONTROLLABLE self -- @param Wrapper.Group#GROUP AttackGroup Target GROUP object. -- @param #number WeaponType Bitmask of weapon types, which are allowed to use. @@ -1489,7 +1487,7 @@ end -- @param #boolean Datalink (Optional) Allows to use datalink to send the target information to attack aircraft. Enabled by default. -- @param #number Frequency Frequency in MHz used to communicate with the FAC. Default 133 MHz. -- @param #number Modulation Modulation of radio for communication. Default 0=AM. --- @param #number CallsignName Callsign enumerator name of the FAC. +-- @param #number CallsignName Callsign enumerator name of the FAC. (CALLSIGN.Aircraft.{name} for airborne controllables, CALLSIGN.JTACS.{name} for ground units) -- @param #number CallsignNumber Callsign number, e.g. Axeman-**1**. -- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation, Datalink, Frequency, Modulation, CallsignName, CallsignNumber ) @@ -1811,7 +1809,7 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) -- DCS task. local DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript ))) - + return DCSTask end @@ -1853,8 +1851,27 @@ do -- Patrol methods -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() - local From = FromCoord:WaypointGround( 120 ) - + + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end + + + local Waypoint = Waypoints[1] + local Speed = Waypoint.speed or (20 / 3.6) + local From = FromCoord:WaypointGround( Speed ) + + if IsSub then + From = FromCoord:WaypointNaval( Speed, Waypoint.alt ) + end + table.insert( Waypoints, 1, From ) local TaskRoute = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRoute" ) @@ -1894,7 +1911,16 @@ do -- Patrol methods if ToWaypoint then FromWaypoint = ToWaypoint end - + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end -- Loop until a waypoint has been found that is not the same as the current waypoint. -- Otherwise the object zon't move or drive in circles and the algorithm would not do exactly -- what it is supposed to do, which is making groups drive around. @@ -1909,9 +1935,13 @@ do -- Patrol methods local ToCoord = COORDINATE:NewFromVec2( { x = Waypoint.x, y = Waypoint.y } ) -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - + if IsSub then + Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route+1] = ToCoord:WaypointNaval( Speed, Waypoint.alt ) + else + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolRouteRandom", Speed, Formation, ToWaypoint ) @@ -1952,9 +1982,20 @@ do -- Patrol methods self:F( { PatrolGroup = PatrolGroup:GetName() } ) if PatrolGroup:IsGround() or PatrolGroup:IsShip() then - + -- Calculate the new Route. local FromCoord = PatrolGroup:GetCoordinate() + + -- test for submarine + local depth = 0 + local IsSub = false + if PatrolGroup:IsShip() then + local navalvec3 = FromCoord:GetVec3() + if navalvec3.y < 0 then + depth = navalvec3.y + IsSub = true + end + end -- Select a random Zone and get the Coordinate of the new Zone. local RandomZone = ZoneList[ math.random( 1, #ZoneList ) ] -- Core.Zone#ZONE @@ -1962,9 +2003,13 @@ do -- Patrol methods -- Create a "ground route point", which is a "point" structure that can be given as a parameter to a Task local Route = {} - Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) - Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) - + if IsSub then + Route[#Route+1] = FromCoord:WaypointNaval( Speed, depth ) + Route[#Route+1] = ToCoord:WaypointNaval( Speed, depth ) + else + Route[#Route+1] = FromCoord:WaypointGround( Speed, Formation ) + Route[#Route+1] = ToCoord:WaypointGround( Speed, Formation ) + end local TaskRouteToZone = PatrolGroup:TaskFunction( "CONTROLLABLE.PatrolZones", ZoneList, Speed, Formation, DelayMin, DelayMax ) @@ -1988,7 +2033,7 @@ function CONTROLLABLE:TaskRoute( Points ) id = 'Mission', params = { airborne = self:IsAir(), - route = {points = Points}, + route = {points = Points}, }, } @@ -2914,9 +2959,9 @@ end function CONTROLLABLE:OptionROE(ROEvalue) local DCSControllable = self:GetDCSObject() - + if DCSControllable then - + local Controller = self:_GetController() if self:IsAir() then @@ -3480,13 +3525,13 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionProhibitAfterburner(Prohibit) self:F2( { self.ControllableName } ) - + if Prohibit==nil then Prohibit=true end if self:IsAir() then - self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) + self:SetOption(AI.Option.Air.id.PROHIBIT_AB, Prohibit) end return self @@ -3497,9 +3542,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_Never() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 0) + self:SetOption(AI.Option.Air.id.ECM_USING, 0) end return self @@ -3510,9 +3555,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_OnlyLockByRadar() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 1) + self:SetOption(AI.Option.Air.id.ECM_USING, 1) end return self @@ -3524,9 +3569,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_DetectedLockByRadar() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 2) + self:SetOption(AI.Option.Air.id.ECM_USING, 2) end return self @@ -3537,9 +3582,9 @@ end -- @return #CONTROLLABLE self function CONTROLLABLE:OptionECM_AlwaysOn() self:F2( { self.ControllableName } ) - + if self:IsAir() then - self:SetOption(AI.Option.Air.id.ECM_USING, 3) + self:SetOption(AI.Option.Air.id.ECM_USING, 3) end return self @@ -3681,22 +3726,22 @@ end --- Sets Controllable Option for A2A attack range for AIR FIGHTER units. -- @param #CONTROLLABLE self --- @param #number range Defines the range +-- @param #number range Defines the range -- @return #CONTROLLABLE self -- @usage Range can be one of MAX_RANGE = 0, NEZ_RANGE = 1, HALF_WAY_RMAX_NEZ = 2, TARGET_THREAT_EST = 3, RANDOM_RANGE = 4. Defaults to 3. See: https://wiki.hoggitworld.com/view/DCS_option_missileAttack function CONTROLLABLE:OptionAAAttackRange(range) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- defaults to 3 local range = range or 3 if range < 0 or range > 4 then range = 3 - end + end local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsAir() then - self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) + self:SetOption(AI.Option.Air.val.MISSILE_ATTACK, range) end end return self @@ -3709,7 +3754,7 @@ end -- @param #number EngageRange Engage range limit in percent (a number between 0 and 100). Default 100. -- @return #CONTROLLABLE self function CONTROLLABLE:OptionEngageRange(EngageRange) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- Set default if not specified. EngageRange=EngageRange or 100 if EngageRange < 0 or EngageRange > 100 then @@ -3718,9 +3763,9 @@ function CONTROLLABLE:OptionEngageRange(EngageRange) local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsGround() then - self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) + self:SetOption(AI.Option.Ground.id.AC_ENGAGEMENT_RANGE_RESTRICTION, EngageRange) end end return self @@ -3736,9 +3781,9 @@ end -- @param #boolean shortcut If true and onroad is set, take a shorter route - if available - off road, default false -- @return #CONTROLLABLE self function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortcut) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) - local _coord = self:GetCoordinate() + local _coord = self:GetCoordinate() local _radius = radius or 500 local _speed = speed or 20 local _tocoord = _coord:GetRandomCoordinateInRadius(_radius,100) @@ -3746,7 +3791,7 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc local _grptsk = {} local _candoroad = false local _shortcut = shortcut or false - + -- create a DCS Task an push it on the group -- TaskGroundOnRoad(ToCoordinate,Speed,OffRoadFormation,Shortcut,FromCoordinate,WaypointFunction,WaypointFunctionArguments) if onroad then @@ -3756,23 +3801,23 @@ function CONTROLLABLE:RelocateGroundRandomInRadius(speed, radius, onroad, shortc self:TaskRouteToVec2(_tocoord:GetVec2(),_speed,"Off Road") end - return self + return self end --- Defines how long a GROUND unit/group will move to avoid an ongoing attack. -- @param #CONTROLLABLE self --- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. +-- @param #number Seconds Any positive number: AI will disperse, but only for the specified time before continuing their route. 0: AI will not disperse. -- @return #CONTROLLABLE self function CONTROLLABLE:OptionDisperseOnAttack(Seconds) - self:F2( { self.ControllableName } ) + self:F2( { self.ControllableName } ) -- Set default if not specified. local seconds = Seconds or 0 local DCSControllable = self:GetDCSObject() if DCSControllable then local Controller = self:_GetController() - if Controller then + if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) end end return self From 85fef96d0038f3573c4baba3ba4489ca02e5f777 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 24 May 2021 10:00:16 +0200 Subject: [PATCH 2/6] ZONE_POLYGON_BASE:Boundary added (#1537) * ZONE_POLYGON_BASE:Boundary added ZONE_POLYGON_BASE:Boundary added * Update Zone.lua Change Radius default value --- Moose Development/Moose/Core/Zone.lua | 749 +++++++++++++++----------- 1 file changed, 446 insertions(+), 303 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8ad723486..65ac3cb9d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1,9 +1,9 @@ --- **Core** - Define zones within your mission of various forms, with various capabilities. --- +-- -- === --- +-- -- ## Features: --- +-- -- * Create radius zones. -- * Create trigger zones. -- * Create polygon zones. @@ -18,24 +18,24 @@ -- * Get zone bounding box. -- * Set/get zone name. -- * Draw zones (circular and polygon) on the F10 map. --- --- +-- +-- -- There are essentially two core functions that zones accomodate: --- +-- -- * Test if an object is within the zone boundaries. -- * Provide the zone behaviour. Some zones are static, while others are moveable. --- +-- -- The object classes are using the zone classes to test the zone boundaries, which can take various forms: --- +-- -- * Test if completely within the zone. -- * Test if partly within the zone (for @{Wrapper.Group#GROUP} objects). -- * Test if not in the zone. -- * Distance to the nearest intersecting point of the zone. -- * Distance to the center of the zone. -- * ... --- +-- -- Each of these ZONE classes have a zone name, and specific parameters defining the zone type: --- +-- -- * @{#ZONE_BASE}: The ZONE_BASE class defining the base for all other zone classes. -- * @{#ZONE_RADIUS}: The ZONE_RADIUS class defined by a zone name, a location and a radius. -- * @{#ZONE}: The ZONE class, defined by the zone name as defined within the Mission Editor. @@ -43,15 +43,15 @@ -- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. -- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- --- === --- --- ### Author: **FlightControl** --- ### Contributions: --- -- === --- +-- +-- ### Author: **FlightControl** +-- ### Contributions: +-- +-- === +-- -- @module Core.Zone --- @image Core_Zones.JPG +-- @image Core_Zones.JPG --- @type ZONE_BASE @@ -63,28 +63,28 @@ --- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- +-- -- ## Each zone has a name: --- +-- -- * @{#ZONE_BASE.GetName}(): Returns the name of the zone. -- * @{#ZONE_BASE.SetName}(): Sets the name of the zone. --- --- +-- +-- -- ## Each zone implements two polymorphic functions defined in @{Core.Zone#ZONE_BASE}: --- +-- -- * @{#ZONE_BASE.IsVec2InZone}(): Returns if a 2D vector is within the zone. -- * @{#ZONE_BASE.IsVec3InZone}(): Returns if a 3D vector is within the zone. -- * @{#ZONE_BASE.IsPointVec2InZone}(): Returns if a 2D point vector is within the zone. -- * @{#ZONE_BASE.IsPointVec3InZone}(): Returns if a 3D point vector is within the zone. --- +-- -- ## A zone has a probability factor that can be set to randomize a selection between zones: --- +-- -- * @{#ZONE_BASE.SetZoneProbability}(): Set the randomization probability of a zone to be selected, taking a value between 0 and 1 ( 0 = 0%, 1 = 100% ) -- * @{#ZONE_BASE.GetZoneProbability}(): Get the randomization probability of a zone to be selected, passing a value between 0 and 1 ( 0 = 0%, 1 = 100% ) -- * @{#ZONE_BASE.GetZoneMaybe}(): Get the zone taking into account the randomization probability. nil is returned if this zone is not a candidate. --- +-- -- ## A zone manages vectors: --- +-- -- * @{#ZONE_BASE.GetVec2}(): Returns the 2D vector coordinate of the zone. -- * @{#ZONE_BASE.GetVec3}(): Returns the 3D vector coordinate of the zone. -- * @{#ZONE_BASE.GetPointVec2}(): Returns the 2D point vector coordinate of the zone. @@ -92,16 +92,16 @@ -- * @{#ZONE_BASE.GetRandomVec2}(): Define a random 2D vector within the zone. -- * @{#ZONE_BASE.GetRandomPointVec2}(): Define a random 2D point vector within the zone. -- * @{#ZONE_BASE.GetRandomPointVec3}(): Define a random 3D point vector within the zone. --- +-- -- ## A zone has a bounding square: --- +-- -- * @{#ZONE_BASE.GetBoundingSquare}(): Get the outer most bounding square of the zone. --- --- ## A zone can be marked: --- +-- +-- ## A zone can be marked: +-- -- * @{#ZONE_BASE.SmokeZone}(): Smokes the zone boundaries in a color. -- * @{#ZONE_BASE.FlareZone}(): Flares the zone boundaries in a color. --- +-- -- @field #ZONE_BASE ZONE_BASE = { ClassName = "ZONE_BASE", @@ -129,7 +129,7 @@ function ZONE_BASE:New( ZoneName ) self:F( ZoneName ) self.ZoneName = ZoneName - + return self end @@ -206,7 +206,7 @@ end -- @param #ZONE_BASE self -- @return #nil. function ZONE_BASE:GetVec2() - return nil + return nil end --- Returns a @{Core.Point#POINT_VEC2} of the zone. @@ -215,30 +215,14 @@ end -- @return Core.Point#POINT_VEC2 The PointVec2 of the zone. function ZONE_BASE:GetPointVec2() self:F2( self.ZoneName ) - + local Vec2 = self:GetVec2() local PointVec2 = POINT_VEC2:NewFromVec2( Vec2 ) self:T2( { PointVec2 } ) - - return PointVec2 -end - ---- Returns a @{Core.Point#COORDINATE} of the zone. --- @param #ZONE_BASE self --- @return Core.Point#COORDINATE The Coordinate of the zone. -function ZONE_BASE:GetCoordinate() - self:F2( self.ZoneName ) - - local Vec2 = self:GetVec2() - - local Coordinate = COORDINATE:NewFromVec2( Vec2 ) - - self:T2( { Coordinate } ) - - return Coordinate + return PointVec2 end @@ -248,16 +232,16 @@ end -- @return DCS#Vec3 The Vec3 of the zone. function ZONE_BASE:GetVec3( Height ) self:F2( self.ZoneName ) - + Height = Height or 0 - + local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = Height and Height or land.getHeight( self:GetVec2() ), z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end --- Returns a @{Core.Point#POINT_VEC3} of the zone. @@ -266,14 +250,14 @@ end -- @return Core.Point#POINT_VEC3 The PointVec3 of the zone. function ZONE_BASE:GetPointVec3( Height ) self:F2( self.ZoneName ) - + local Vec3 = self:GetVec3( Height ) local PointVec3 = POINT_VEC3:NewFromVec3( Vec3 ) self:T2( { PointVec3 } ) - - return PointVec3 + + return PointVec3 end --- Returns a @{Core.Point#COORDINATE} of the zone. @@ -281,15 +265,27 @@ end -- @param DCS#Distance Height The height to add to the land height where the center of the zone is located. -- @return Core.Point#COORDINATE The Coordinate of the zone. function ZONE_BASE:GetCoordinate( Height ) --R2.1 - self:F2( self.ZoneName ) - + self:F2(self.ZoneName) + local Vec3 = self:GetVec3( Height ) - local PointVec3 = COORDINATE:NewFromVec3( Vec3 ) + if self.Coordinate then - self:T2( { PointVec3 } ) - - return PointVec3 + -- Update coordinates. + self.Coordinate.x=Vec3.x + self.Coordinate.y=Vec3.y + self.Coordinate.z=Vec3.z + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + else + + -- Create a new coordinate object. + self.Coordinate=COORDINATE:NewFromVec3(Vec3) + + --env.info("FF GetCoordinate NEW for ZONE_BASE "..tostring(self.ZoneName)) + end + + return self.Coordinate end @@ -339,7 +335,7 @@ function ZONE_BASE:SetColor(RGBcolor, Alpha) RGBcolor=RGBcolor or {1, 0, 0} Alpha=Alpha or 0.15 - + self.Color={} self.Color[1]=RGBcolor[1] self.Color[2]=RGBcolor[2] @@ -375,7 +371,7 @@ function ZONE_BASE:GetColorAlpha() return alpha end ---- Remove the drawing of the zone from the F10 map. +--- Remove the drawing of the zone from the F10 map. -- @param #ZONE_BASE self -- @param #number Delay (Optional) Delay before the drawing is removed. -- @return #ZONE_BASE self @@ -391,7 +387,7 @@ function ZONE_BASE:UndrawZone(Delay) end --- Get ID of the zone object drawn on the F10 map. --- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. +-- The ID can be used to remove the drawn object from the F10 map view via `UTILS.RemoveMark(MarkID)`. -- @param #ZONE_BASE self -- @return #number Unique ID of the function ZONE_BASE:GetDrawID() @@ -404,7 +400,7 @@ end -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) - + end --- Set the randomization probability of a zone to be selected. @@ -413,7 +409,7 @@ end -- @return #ZONE_BASE self function ZONE_BASE:SetZoneProbability( ZoneProbability ) self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) - + self.ZoneProbability = ZoneProbability or 1 return self end @@ -422,8 +418,8 @@ end -- @param #ZONE_BASE self -- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:GetZoneProbability() - self:F2() - + self:F2() + return self.ZoneProbability end @@ -432,15 +428,15 @@ end -- @return #ZONE_BASE The zone is selected taking into account the randomization probability factor. -- @return #nil The zone is not selected taking into account the randomization probability factor. -- @usage --- +-- -- local ZoneArray = { ZONE:New( "Zone1" ), ZONE:New( "Zone2" ) } --- +-- -- -- We set a zone probability of 70% to the first zone and 30% to the second zone. -- ZoneArray[1]:SetZoneProbability( 0.5 ) -- ZoneArray[2]:SetZoneProbability( 0.5 ) --- +-- -- local ZoneSelected = nil --- +-- -- while ZoneSelected == nil do -- for _, Zone in pairs( ZoneArray ) do -- ZoneSelected = Zone:GetZoneMaybe() @@ -449,12 +445,12 @@ end -- end -- end -- end --- +-- -- -- The result should be that Zone1 would be more probable selected than Zone2. --- +-- function ZONE_BASE:GetZoneMaybe() self:F2() - + local Randomization = math.random() if Randomization <= self.ZoneProbability then return self @@ -472,34 +468,34 @@ end --- The ZONE_RADIUS class defined by a zone name, a location and a radius. -- This class implements the inherited functions from Core.Zone#ZONE_BASE taking into account the own zone format and properties. --- +-- -- ## ZONE_RADIUS constructor --- +-- -- * @{#ZONE_RADIUS.New}(): Constructor. --- +-- -- ## Manage the radius of the zone --- +-- -- * @{#ZONE_RADIUS.SetRadius}(): Sets the radius of the zone. -- * @{#ZONE_RADIUS.GetRadius}(): Returns the radius of the zone. --- +-- -- ## Manage the location of the zone --- +-- -- * @{#ZONE_RADIUS.SetVec2}(): Sets the @{DCS#Vec2} of the zone. -- * @{#ZONE_RADIUS.GetVec2}(): Returns the @{DCS#Vec2} of the zone. -- * @{#ZONE_RADIUS.GetVec3}(): Returns the @{DCS#Vec3} of the zone, taking an additional height parameter. --- +-- -- ## Zone point randomization --- +-- -- Various functions exist to find random points within the zone. --- +-- -- * @{#ZONE_RADIUS.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec2}(): Gets a @{Core.Point#POINT_VEC2} object representing a random 2D point in the zone. -- * @{#ZONE_RADIUS.GetRandomPointVec3}(): Gets a @{Core.Point#POINT_VEC3} object representing a random 3D point in the zone. Note that the height of the point is at landheight. --- +-- -- ## Draw zone --- +-- -- * @{#ZONE_RADIUS.DrawZone}(): Draws the zone on the F10 map. --- +-- -- @field #ZONE_RADIUS ZONE_RADIUS = { ClassName="ZONE_RADIUS", @@ -512,15 +508,54 @@ ZONE_RADIUS = { -- @param DCS#Distance Radius The radius of the zone. -- @return #ZONE_RADIUS self function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) -- #ZONE_RADIUS self:F( { ZoneName, Vec2, Radius } ) self.Radius = Radius self.Vec2 = Vec2 - + + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) + return self end +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec2 Vec2 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec2(Vec2, Radius) + + -- New center of the zone. + self.Vec2=Vec2 + + if Radius then + self.Radius=Radius + end + + return self +end + +--- Update zone from a 2D vector. +-- @param #ZONE_RADIUS self +-- @param DCS#Vec3 Vec3 The location of the zone. +-- @param DCS#Distance Radius The radius of the zone. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:UpdateFromVec3(Vec3, Radius) + + -- New center of the zone. + self.Vec2.x=Vec3.x + self.Vec2.y=Vec3.z + + if Radius then + self.Radius=Radius + end + + return self +end + --- Mark the zone with markers on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Points (Optional) The amount of points in the circle. Default 360. @@ -534,21 +569,21 @@ function ZONE_RADIUS:MarkZone(Points) local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, (360 / Points ) do - + local Radial = Angle * RadialBase / 360 - + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - + COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) end - + end ---- Draw the zone circle on the F10 map. +--- Draw the zone circle on the F10 map. -- @param #ZONE_RADIUS self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. @@ -561,16 +596,16 @@ end function ZONE_RADIUS:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) local coordinate=self:GetCoordinate() - + local Radius=self:GetRadius() - + Color=Color or self:GetColorRGB() Alpha=Alpha or 1 FillColor=FillColor or Color FillAlpha=FillAlpha or self:GetColorAlpha() self.DrawID=coordinate:CircleToAll(Radius, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + return self end @@ -589,17 +624,17 @@ function ZONE_RADIUS:BoundZone( Points, CountryID, UnBound ) local Angle local RadialBase = math.pi*2 - + -- for Angle = 0, 360, (360 / Points ) do local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() - + local CountryName = _DATABASE.COUNTRY_NAME[CountryID] - + local Tire = { - ["country"] = CountryName, + ["country"] = CountryName, ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -633,7 +668,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Point = {} local Vec2 = self:GetVec2() - + AddHeight = AddHeight or 0 AngleOffset = AngleOffset or 0 @@ -641,7 +676,7 @@ function ZONE_RADIUS:SmokeZone( SmokeColor, Points, AddHeight, AngleOffset ) local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, 360 / Points do local Radial = ( Angle + AngleOffset ) * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() @@ -665,14 +700,14 @@ function ZONE_RADIUS:FlareZone( FlareColor, Points, Azimuth, AddHeight ) local Point = {} local Vec2 = self:GetVec2() - + AddHeight = AddHeight or 0 - + Points = Points and Points or 360 local Angle local RadialBase = math.pi*2 - + for Angle = 0, 360, 360 / Points do local Radial = Angle * RadialBase / 360 Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() @@ -714,8 +749,8 @@ function ZONE_RADIUS:GetVec2() self:F2( self.ZoneName ) self:T2( { self.Vec2 } ) - - return self.Vec2 + + return self.Vec2 end --- Sets the @{DCS#Vec2} of the zone. @@ -724,12 +759,12 @@ end -- @return DCS#Vec2 The new location of the zone. function ZONE_RADIUS:SetVec2( Vec2 ) self:F2( self.ZoneName ) - + self.Vec2 = Vec2 self:T2( { self.Vec2 } ) - - return self.Vec2 + + return self.Vec2 end --- Returns the @{DCS#Vec3} of the ZONE_RADIUS. @@ -745,8 +780,8 @@ function ZONE_RADIUS:GetVec3( Height ) local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end @@ -755,7 +790,7 @@ end --- Scan the zone for the presence of units of the given ObjectCategories. -- Note that after a zone has been scanned, the zone can be evaluated by: --- +-- -- * @{ZONE_RADIUS.IsAllInZoneOfCoalition}(): Scan the presence of units in the zone of a coalition. -- * @{ZONE_RADIUS.IsAllInZoneOfOtherCoalition}(): Scan the presence of units in the zone of an other coalition. -- * @{ZONE_RADIUS.IsSomeInZoneOfCoalition}(): Scan if there is some presence of units in the zone of the given coalition. @@ -777,7 +812,7 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { @@ -790,18 +825,18 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) local function EvaluateZone( ZoneObject ) --if ZoneObject:isExist() then --FF: isExist always returns false for SCENERY objects since DCS 2.2 and still in DCS 2.5 - if ZoneObject then - + if ZoneObject then + local ObjectCategory = ZoneObject:getCategory() - + --local name=ZoneObject:getName() --env.info(string.format("Zone object %s", tostring(name))) --self:E(ZoneObject) - + if ( ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive() ) or (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then - + local CoalitionDCSUnit = ZoneObject:getCoalition() - + local Include = false if not UnitCategories then -- Anythink found is included. @@ -809,29 +844,29 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) else -- Check if found object is in specified categories. local CategoryDCSUnit = ZoneObject:getDesc().category - + for UnitCategoryID, UnitCategory in pairs( UnitCategories ) do if UnitCategory == CategoryDCSUnit then Include = true break end end - + end - + if Include then - + local CoalitionDCSUnit = ZoneObject:getCoalition() - + -- This coalition is inside the zone. self.ScanData.Coalitions[CoalitionDCSUnit] = true - + self.ScanData.Units[ZoneObject] = ZoneObject - + self:F2( { Name = ZoneObject:getName(), Coalition = CoalitionDCSUnit } ) end end - + if ObjectCategory == Object.Category.SCENERY then local SceneryType = ZoneObject:getTypeName() local SceneryName = ZoneObject:getName() @@ -839,15 +874,15 @@ function ZONE_RADIUS:Scan( ObjectCategories, UnitCategories ) self.ScanData.Scenery[SceneryType][SceneryName] = SCENERY:Register( SceneryName, ZoneObject ) self:F2( { SCENERY = self.ScanData.Scenery[SceneryType][SceneryName] } ) end - + end - + return true end -- Search objects. world.searchObjects( ObjectCategories, SphereSearch, EvaluateZone ) - + end --- Count the number of different coalitions inside the zone. @@ -886,6 +921,32 @@ function ZONE_RADIUS:GetScannedSetUnit() return SetUnit end +--- Get a set of scanned units. +-- @param #ZONE_RADIUS self +-- @return Core.Set#SET_GROUP Set of groups. +function ZONE_RADIUS:GetScannedSetGroup() + + self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP + + self.ScanSetGroup.Set={} + + if self.ScanData then + for ObjectID, UnitObject in pairs( self.ScanData.Units ) do + local UnitObject = UnitObject -- DCS#Unit + if UnitObject:isExist() then + + local FoundUnit=UNIT:FindByName(UnitObject:getName()) + if FoundUnit then + local group=FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup(group) + end + end + end + end + + return self.ScanSetGroup +end + --- Count the number of different coalitions inside the zone. -- @param #ZONE_RADIUS self @@ -893,11 +954,11 @@ end function ZONE_RADIUS:CountScannedCoalitions() local Count = 0 - + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do Count = Count + 1 end - + return Count end @@ -925,16 +986,16 @@ function ZONE_RADIUS:GetScannedCoalition( Coalition ) else local Count = 0 local ReturnCoalition = nil - + for CoalitionID, Coalition in pairs( self.ScanData.Coalitions ) do Count = Count + 1 ReturnCoalition = CoalitionID end - + if Count ~= 1 then ReturnCoalition = nil end - + return ReturnCoalition end end @@ -1045,7 +1106,7 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) local ZoneCoord = self:GetCoordinate() local ZoneRadius = self:GetRadius() - + self:F({ZoneCoord = ZoneCoord, ZoneRadius = ZoneRadius, ZoneCoordLL = ZoneCoord:ToStringLLDMS()}) local SphereSearch = { @@ -1057,8 +1118,8 @@ function ZONE_RADIUS:SearchZone( EvaluateFunction, ObjectCategories ) } local function EvaluateZone( ZoneDCSUnit ) - - + + local ZoneUnit = UNIT:Find( ZoneDCSUnit ) return EvaluateFunction( ZoneUnit ) @@ -1074,15 +1135,15 @@ end -- @return #boolean true if the location is within the zone. function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - + local ZoneVec2 = self:GetVec2() - + if ZoneVec2 then if (( Vec2.x - ZoneVec2.x )^2 + ( Vec2.y - ZoneVec2.y ) ^2 ) ^ 0.5 <= self:GetRadius() then return true end end - + return false end @@ -1114,9 +1175,9 @@ function ZONE_RADIUS:GetRandomVec2( inner, outer ) local angle = math.random() * math.pi * 2; Point.x = Vec2.x + math.cos( angle ) * math.random(_inner, _outer); Point.y = Vec2.y + math.sin( angle ) * math.random(_inner, _outer); - + self:T( { Point } ) - + return Point end @@ -1131,7 +1192,7 @@ function ZONE_RADIUS:GetRandomPointVec2( inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { PointVec2 } ) - + return PointVec2 end @@ -1146,7 +1207,7 @@ function ZONE_RADIUS:GetRandomVec3( inner, outer ) local Vec2 = self:GetRandomVec2( inner, outer ) self:T3( { x = Vec2.x, y = self.y, z = Vec2.y } ) - + return { x = Vec2.x, y = self.y, z = Vec2.y } end @@ -1162,7 +1223,7 @@ function ZONE_RADIUS:GetRandomPointVec3( inner, outer ) local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2( inner, outer ) ) self:T3( { PointVec3 } ) - + return PointVec3 end @@ -1178,7 +1239,7 @@ function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) self:T3( { Coordinate = Coordinate } ) - + return Coordinate end @@ -1190,32 +1251,32 @@ end --- The ZONE class, defined by the zone name as defined within the Mission Editor. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- ## ZONE constructor --- +-- -- * @{#ZONE.New}(): Constructor. This will search for a trigger zone with the name given, and will return for you a ZONE object. --- +-- -- ## Declare a ZONE directly in the DCS mission editor! --- +-- -- You can declare a ZONE using the DCS mission editor by adding a trigger zone in the mission editor. --- +-- -- Then during mission startup, when loading Moose.lua, this trigger zone will be detected as a ZONE declaration. -- Within the background, a ZONE object will be created within the @{Core.Database}. -- The ZONE name will be the trigger zone name. --- +-- -- So, you can search yourself for the ZONE object by using the @{#ZONE.FindByName}() method. -- In this example, `local TriggerZone = ZONE:FindByName( "DefenseZone" )` would return the ZONE object --- that was created at mission startup, and reference it into the `TriggerZone` local object. --- +-- that was created at mission startup, and reference it into the `TriggerZone` local object. +-- -- Refer to mission `ZON-110` for a demonstration. --- +-- -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE object `DefenseZone` as part of the zone collection, --- without much scripting overhead!!! --- --- --- @field #ZONE +-- without much scripting overhead!!! +-- +-- +-- @field #ZONE ZONE = { ClassName="ZONE", } @@ -1229,7 +1290,7 @@ function ZONE:New( ZoneName ) -- First try to find the zone in the DB. local zone=_DATABASE:FindZone(ZoneName) - + if zone then --env.info("FF found zone in DB") return zone @@ -1237,7 +1298,7 @@ function ZONE:New( ZoneName ) -- Get zone from DCS trigger function. local Zone = trigger.misc.getZone( ZoneName ) - + -- Error! if not Zone then error( "Zone " .. ZoneName .. " does not exist." ) @@ -1247,13 +1308,13 @@ function ZONE:New( ZoneName ) -- Create a new ZONE_RADIUS. local self=BASE:Inherit( self, ZONE_RADIUS:New(ZoneName, {x=Zone.point.x, y=Zone.point.z}, Zone.radius)) self:F(ZoneName) - + -- Color of zone. self.Color={1, 0, 0, 0.15} -- DCS zone. self.Zone = Zone - + return self end @@ -1262,7 +1323,7 @@ end -- @param #string ZoneName The name of the zone. -- @return #ZONE_BASE self function ZONE:FindByName( ZoneName ) - + local ZoneFound = _DATABASE:FindZone( ZoneName ) return ZoneFound end @@ -1275,15 +1336,15 @@ end --- # ZONE_UNIT class, extends @{Zone#ZONE_RADIUS} --- +-- -- The ZONE_UNIT class defined by a zone attached to a @{Wrapper.Unit#UNIT} with a radius and optional offsets. -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- @field #ZONE_UNIT ZONE_UNIT = { ClassName="ZONE_UNIT", } - + --- Constructor to create a ZONE_UNIT instance, taking the zone name, a zone unit and a radius and optional offsets in X and Y directions. -- @param #ZONE_UNIT self -- @param #string ZoneName Name of the zone. @@ -1298,30 +1359,30 @@ ZONE_UNIT = { -- dx, dy OR rho, theta may be used, not both. -- @return #ZONE_UNIT self function ZONE_UNIT:New( ZoneName, ZoneUNIT, Radius, Offset) - + if Offset then - -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. + -- check if the inputs was reasonable, either (dx, dy) or (rho, theta) can be given, else raise an exception. if (Offset.dx or Offset.dy) and (Offset.rho or Offset.theta) then - error("Cannot use (dx, dy) with (rho, theta)") + error("Cannot use (dx, dy) with (rho, theta)") end - + self.dy = Offset.dy or 0.0 self.dx = Offset.dx or 0.0 self.rho = Offset.rho or 0.0 self.theta = (Offset.theta or 0.0) * math.pi / 180.0 self.relative_to_unit = Offset.relative_to_unit or false end - + local self = BASE:Inherit( self, ZONE_RADIUS:New( ZoneName, ZoneUNIT:GetVec2(), Radius ) ) self:F( { ZoneName, ZoneUNIT:GetVec2(), Radius } ) self.ZoneUNIT = ZoneUNIT self.LastVec2 = ZoneUNIT:GetVec2() - + -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end @@ -1331,32 +1392,32 @@ end -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Unit#UNIT}location and the offset, if any. function ZONE_UNIT:GetVec2() self:F2( self.ZoneName ) - + local ZoneVec2 = self.ZoneUNIT:GetVec2() if ZoneVec2 then - + local heading if self.relative_to_unit then heading = ( self.ZoneUNIT:GetHeading() or 0.0 ) * math.pi / 180.0 else heading = 0.0 end - + -- update the zone position with the offsets. if (self.dx or self.dy) then - + -- use heading to rotate offset relative to unit using rotation matrix in 2D. -- see: https://en.wikipedia.org/wiki/Rotation_matrix - ZoneVec2.x = ZoneVec2.x + self.dx * math.cos( -heading ) + self.dy * math.sin( -heading ) - ZoneVec2.y = ZoneVec2.y - self.dx * math.sin( -heading ) + self.dy * math.cos( -heading ) + ZoneVec2.x = ZoneVec2.x + self.dx * math.cos( -heading ) + self.dy * math.sin( -heading ) + ZoneVec2.y = ZoneVec2.y - self.dx * math.sin( -heading ) + self.dy * math.cos( -heading ) end - + -- if using the polar coordinates - if (self.rho or self.theta) then + if (self.rho or self.theta) then ZoneVec2.x = ZoneVec2.x + self.rho * math.cos( self.theta + heading ) ZoneVec2.y = ZoneVec2.y + self.rho * math.sin( self.theta + heading ) end - + self.LastVec2 = ZoneVec2 return ZoneVec2 else @@ -1365,7 +1426,7 @@ function ZONE_UNIT:GetVec2() self:T2( { ZoneVec2 } ) - return nil + return nil end --- Returns a random location within the zone. @@ -1377,7 +1438,7 @@ function ZONE_UNIT:GetRandomVec2() local RandomVec2 = {} --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! local Vec2 = self:GetVec2() - + if not Vec2 then Vec2 = self.LastVec2 end @@ -1385,9 +1446,9 @@ function ZONE_UNIT:GetRandomVec2() local angle = math.random() * math.pi*2; RandomVec2.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); RandomVec2.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { RandomVec2 } ) - + return RandomVec2 end @@ -1397,16 +1458,16 @@ end -- @return DCS#Vec3 The point of the zone. function ZONE_UNIT:GetVec3( Height ) self:F2( self.ZoneName ) - + Height = Height or 0 - + local Vec2 = self:GetVec2() local Vec3 = { x = Vec2.x, y = land.getHeight( self:GetVec2() ) + Height, z = Vec2.y } self:T2( { Vec3 } ) - - return Vec3 + + return Vec3 end --- @type ZONE_GROUP @@ -1415,12 +1476,12 @@ end --- The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. The current leader of the group defines the center of the zone. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- @field #ZONE_GROUP ZONE_GROUP = { ClassName="ZONE_GROUP", } - + --- Constructor to create a ZONE_GROUP instance, taking the zone name, a zone @{Wrapper.Group#GROUP} and a radius. -- @param #ZONE_GROUP self -- @param #string ZoneName Name of the zone. @@ -1436,7 +1497,7 @@ function ZONE_GROUP:New( ZoneName, ZoneGROUP, Radius ) -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end @@ -1446,9 +1507,9 @@ end -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_GROUP:GetVec2() self:F( self.ZoneName ) - + local ZoneVec2 = nil - + if self._.ZoneGROUP:IsAlive() then ZoneVec2 = self._.ZoneGROUP:GetVec2() self._.ZoneVec2Cache = ZoneVec2 @@ -1457,7 +1518,7 @@ function ZONE_GROUP:GetVec2() end self:T( { ZoneVec2 } ) - + return ZoneVec2 end @@ -1473,9 +1534,9 @@ function ZONE_GROUP:GetRandomVec2() local angle = math.random() * math.pi*2; Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { Point } ) - + return Point end @@ -1490,7 +1551,7 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) self:T3( { PointVec2 } ) - + return PointVec2 end @@ -1503,47 +1564,90 @@ end --- The ZONE_POLYGON_BASE class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. -- This class is an abstract BASE class for derived classes, and is not meant to be instantiated. --- +-- -- ## Zone point randomization --- +-- -- Various functions exist to find random points within the zone. --- +-- -- * @{#ZONE_POLYGON_BASE.GetRandomVec2}(): Gets a random 2D point in the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec2}(): Return a @{Core.Point#POINT_VEC2} object representing a random 2D point within the zone. -- * @{#ZONE_POLYGON_BASE.GetRandomPointVec3}(): Return a @{Core.Point#POINT_VEC3} object representing a random 3D point at landheight within the zone. -- -- ## Draw zone --- --- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. -- --- +-- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles. +-- +-- -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", } ---- A points array. +--- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 --- @list +-- @list Table of 2D vectors. + +--- A 3D points array. +-- @type ZONE_POLYGON_BASE.ListVec3 +-- @list Table of 3D vectors. --- Constructor to create a ZONE_POLYGON_BASE instance, taking the zone name and an array of @{DCS#Vec2}, forming a polygon. -- The @{Wrapper.Group#GROUP} waypoints define the polygon corners. The first and the last point are automatically connected. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName Name of the zone. --- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon.. +-- @param #ZONE_POLYGON_BASE.ListVec2 PointsArray An array of @{DCS#Vec2}, forming a polygon. -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) + + -- Inherit ZONE_BASE. local self = BASE:Inherit( self, ZONE_BASE:New( ZoneName ) ) self:F( { ZoneName, PointsArray } ) - local i = 0 - + if PointsArray then + + self._.Polygon = {} + + for i = 1, #PointsArray do + self._.Polygon[i] = {} + self._.Polygon[i].x = PointsArray[i].x + self._.Polygon[i].y = PointsArray[i].y + end + + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec2}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) + self._.Polygon = {} - - for i = 1, #PointsArray do + + for i=1,#Vec2Array do self._.Polygon[i] = {} - self._.Polygon[i].x = PointsArray[i].x - self._.Polygon[i].y = PointsArray[i].y + self._.Polygon[i].x=Vec2Array[i].x + self._.Polygon[i].y=Vec2Array[i].y + end + + return self +end + +--- Update polygon points with an array of @{DCS#Vec3}. +-- @param #ZONE_POLYGON_BASE self +-- @param #ZONE_POLYGON_BASE.ListVec3 Vec2Array An array of @{DCS#Vec3}, forming a polygon. +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) + + self._.Polygon = {} + + for i=1,#Vec3Array do + self._.Polygon[i] = {} + self._.Polygon[i].x=Vec3Array[i].x + self._.Polygon[i].y=Vec3Array[i].z end return self @@ -1556,13 +1660,13 @@ function ZONE_POLYGON_BASE:GetVec2() self:F( self.ZoneName ) local Bounds = self:GetBoundingSquare() - - return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } + + return { x = ( Bounds.x2 + Bounds.x1 ) / 2, y = ( Bounds.y2 + Bounds.y1 ) / 2 } end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec2 Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexVec2(Index) return self._.Polygon[Index or 1] @@ -1570,7 +1674,7 @@ end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return DCS#Vec3 Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexVec3(Index) local vec2=self:GetVertexVec2(Index) @@ -1583,7 +1687,7 @@ end --- Get a vertex of the polygon. -- @param #ZONE_POLYGON_BASE self --- @param #number Index Index of the vertex. Default 1. +-- @param #number Index Index of the vertex. Default 1. -- @return Core.Point#COORDINATE Vertex of the polygon. function ZONE_POLYGON_BASE:GetVertexCoordinate(Index) local vec2=self:GetVertexVec2(Index) @@ -1613,7 +1717,7 @@ function ZONE_POLYGON_BASE:GetVerticiesVec3() local vec3={x=vec2.x, y=land.getHeight(vec2), z=vec2.y} table.insert(coords, vec3) end - + return coords end @@ -1628,7 +1732,7 @@ function ZONE_POLYGON_BASE:GetVerticiesCoordinates() local coord=COORDINATE:NewFromVec2(vec2) table.insert(coords, coord) end - + return coords end @@ -1649,24 +1753,24 @@ end -- @return #ZONE_POLYGON_BASE self function ZONE_POLYGON_BASE:BoundZone( UnBound ) - local i - local j + local i + local j local Segments = 10 - + i = 1 j = #self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) local Tire = { - ["country"] = "USA", + ["country"] = "USA", ["category"] = "Fortifications", ["canCargo"] = false, ["shape_name"] = "H-tyre_B_WF", @@ -1676,12 +1780,12 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) ["name"] = string.format( "%s-Tire #%0d", self:GetName(), ((i - 1) * Segments) + Segment ), ["heading"] = 0, } -- end of ["group"] - + local Group = coalition.addStaticObject( country.id.USA, Tire ) if UnBound and UnBound == true then Group:destroy() end - + end j = i i = i + 1 @@ -1712,23 +1816,23 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph if #self._.Polygon==4 then - + local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - + self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + else - + local Coordinates=self:GetVerticiesCoordinates() table.remove(Coordinates, 1) self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - + end - - + + return self end @@ -1741,16 +1845,16 @@ function ZONE_POLYGON_BASE:SmokeZone( SmokeColor, Segments ) self:F2( SmokeColor ) Segments=Segments or 10 - + local i=1 local j=#self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) @@ -1775,18 +1879,18 @@ function ZONE_POLYGON_BASE:FlareZone( FlareColor, Segments, Azimuth, AddHeight ) self:F2(FlareColor) Segments=Segments or 10 - + AddHeight = AddHeight or 0 - + local i=1 local j=#self._.Polygon - + while i <= #self._.Polygon do self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) - + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y - + for Segment = 0, Segments do -- We divide each line in 5 segments and smoke a point on the line. local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) @@ -1810,17 +1914,17 @@ end function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - local Next - local Prev + local Next + local Prev local InPolygon = false - + Next = 1 Prev = #self._.Polygon - + while Next <= #self._.Polygon do self:T( { Next, Prev, self._.Polygon[Next], self._.Polygon[Prev] } ) if ( ( ( self._.Polygon[Next].y > Vec2.y ) ~= ( self._.Polygon[Prev].y > Vec2.y ) ) and - ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) + ( Vec2.x < ( self._.Polygon[Prev].x - self._.Polygon[Next].x ) * ( Vec2.y - self._.Polygon[Next].y ) / ( self._.Polygon[Prev].y - self._.Polygon[Next].y ) + self._.Polygon[Next].x ) ) then InPolygon = not InPolygon end @@ -1843,9 +1947,9 @@ function ZONE_POLYGON_BASE:GetRandomVec2() local Vec2Found = false local Vec2 local BS = self:GetBoundingSquare() - + self:T2( BS ) - + while Vec2Found == false do Vec2 = { x = math.random( BS.x1, BS.x2 ), y = math.random( BS.y1, BS.y2 ) } self:T2( Vec2 ) @@ -1853,7 +1957,7 @@ function ZONE_POLYGON_BASE:GetRandomVec2() Vec2Found = true end end - + self:T2( Vec2 ) return Vec2 @@ -1866,7 +1970,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec2() self:F2() local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - + self:T2( PointVec2 ) return PointVec2 @@ -1879,7 +1983,7 @@ function ZONE_POLYGON_BASE:GetRandomPointVec3() self:F2() local PointVec3 = POINT_VEC3:NewFromVec2( self:GetRandomVec2() ) - + self:T2( PointVec3 ) return PointVec3 @@ -1893,7 +1997,7 @@ function ZONE_POLYGON_BASE:GetRandomCoordinate() self:F2() local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() ) - + self:T2( Coordinate ) return Coordinate @@ -1909,19 +2013,58 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() 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 return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } 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. +-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White. +-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000. +-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1. +-- @param #number Segments (Optional) Number of segments within boundary line. Default 10. +-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + for Segment = 0, Segments do + local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) + local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) + ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) + end + end + j = i + i = i + 1 + end + return self +end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE @@ -1929,27 +2072,27 @@ end --- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. --- +-- -- ## Declare a ZONE_POLYGON directly in the DCS mission editor! --- +-- -- You can declare a ZONE_POLYGON using the DCS mission editor by adding the ~ZONE_POLYGON tag in the group name. --- +-- -- So, imagine you have a group declared in the mission editor, with group name `DefenseZone~ZONE_POLYGON`. -- Then during mission startup, when loading Moose.lua, this group will be detected as a ZONE_POLYGON declaration. -- Within the background, a ZONE_POLYGON object will be created within the @{Core.Database} using the properties of the group. -- The ZONE_POLYGON name will be the group name without the ~ZONE_POLYGON tag. --- +-- -- So, you can search yourself for the ZONE_POLYGON by using the @{#ZONE_POLYGON.FindByName}() method. -- In this example, `local PolygonZone = ZONE_POLYGON:FindByName( "DefenseZone" )` would return the ZONE_POLYGON object -- that was created at mission startup, and reference it into the `PolygonZone` local object. --- +-- -- Mission `ZON-510` shows a demonstration of this feature or method. --- +-- -- This is especially handy if you want to quickly setup a SET_ZONE... -- So when you would declare `local SetZone = SET_ZONE:New():FilterPrefixes( "Defense" ):FilterStart()`, -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, -- without much scripting overhead! --- +-- -- @field #ZONE_POLYGON ZONE_POLYGON = { ClassName="ZONE_POLYGON", @@ -2001,7 +2144,7 @@ end -- @param #string ZoneName The name of the polygon zone. -- @return #ZONE_POLYGON self function ZONE_POLYGON:FindByName( ZoneName ) - + local ZoneFound = _DATABASE:FindZone( ZoneName ) return ZoneFound end @@ -2010,85 +2153,85 @@ do -- ZONE_AIRBASE --- @type ZONE_AIRBASE -- @extends #ZONE_RADIUS - - + + --- The ZONE_AIRBASE class defines by a zone around a @{Wrapper.Airbase#AIRBASE} with a radius. -- This class implements the inherited functions from @{Core.Zone#ZONE_RADIUS} taking into account the own zone format and properties. - -- + -- -- @field #ZONE_AIRBASE ZONE_AIRBASE = { ClassName="ZONE_AIRBASE", } - - - + + + --- Constructor to create a ZONE_AIRBASE instance, taking the zone name, a zone @{Wrapper.Airbase#AIRBASE} and a radius. -- @param #ZONE_AIRBASE self -- @param #string AirbaseName Name of the airbase. -- @param DCS#Distance Radius (Optional)The radius of the zone in meters. Default 4000 meters. -- @return #ZONE_AIRBASE self function ZONE_AIRBASE:New( AirbaseName, Radius ) - + Radius=Radius or 4000 - + local Airbase = AIRBASE:FindByName( AirbaseName ) - + local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius ) ) - + self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() - + -- Zone objects are added to the _DATABASE and SET_ZONE objects. _EVENTDISPATCHER:CreateEventNewZone( self ) - + return self end - + --- Get the airbase as part of the ZONE_AIRBASE object. -- @param #ZONE_AIRBASE self -- @return Wrapper.Airbase#AIRBASE The airbase. function ZONE_AIRBASE:GetAirbase() return self._.ZoneAirbase - end - + end + --- Returns the current location of the @{Wrapper.Group}. -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. function ZONE_AIRBASE:GetVec2() self:F( self.ZoneName ) - + local ZoneVec2 = nil - + if self._.ZoneAirbase:IsAlive() then ZoneVec2 = self._.ZoneAirbase:GetVec2() self._.ZoneVec2Cache = ZoneVec2 else ZoneVec2 = self._.ZoneVec2Cache end - + self:T( { ZoneVec2 } ) - + return ZoneVec2 end - + --- Returns a random location within the zone of the @{Wrapper.Group}. -- @param #ZONE_AIRBASE self -- @return DCS#Vec2 The random location of the zone based on the @{Wrapper.Group} location. function ZONE_AIRBASE:GetRandomVec2() self:F( self.ZoneName ) - + local Point = {} local Vec2 = self._.ZoneAirbase:GetVec2() - + local angle = math.random() * math.pi*2; Point.x = Vec2.x + math.cos( angle ) * math.random() * self:GetRadius(); Point.y = Vec2.y + math.sin( angle ) * math.random() * self:GetRadius(); - + self:T( { Point } ) - + return Point end - + --- Returns a @{Core.Point#POINT_VEC2} object reflecting a random 2D location within the zone. -- @param #ZONE_AIRBASE self -- @param #number inner (optional) Minimal distance from the center of the zone. Default is 0. @@ -2096,11 +2239,11 @@ do -- ZONE_AIRBASE -- @return Core.Point#POINT_VEC2 The @{Core.Point#POINT_VEC2} object reflecting the random 3D location within the zone. function ZONE_AIRBASE:GetRandomPointVec2( inner, outer ) self:F( self.ZoneName, inner, outer ) - + local PointVec2 = POINT_VEC2:NewFromVec2( self:GetRandomVec2() ) - + self:T3( { PointVec2 } ) - + return PointVec2 end From 75849fbfb55a21cce177dea0d87d22951e00b010 Mon Sep 17 00:00:00 2001 From: Shadowze <30597073+shadowze@users.noreply.github.com> Date: Tue, 25 May 2021 22:30:16 +0100 Subject: [PATCH 3/6] Update Positionable.lua function added to work out if a unit is a submarine --- .../Moose/Wrapper/Positionable.lua | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 1a12d7206..ac4e49ad9 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -684,6 +684,27 @@ function POSITIONABLE:IsShip() end +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +function POSITIONABLE:IsSubmarine() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end + end + + return nil +end + + --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param Wrapper.Positionable#POSITIONABLE self @@ -817,8 +838,7 @@ end -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) - local velmps=self:GetVelocityMPS() - return UTILS.MpsToKnots(velmps) + return UTILS.MpsToKnots(self:GetVelocityMPS()) end --- Returns the Angle of Attack of a positionable. From 6e37300d9b6cb7039cd307e65f4f2fa409de98c3 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 08:44:30 +0200 Subject: [PATCH 4/6] Update Controllable.lua Added function `IsSubmarine()`removed type from `OptionDisperseOnAttack()` --- .../Moose/Wrapper/Controllable.lua | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 58e1a747e..7f2f1f795 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3817,10 +3817,30 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) local Controller = self:_GetController() if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) end end return self end return nil end + +--- Returns if the unit is a submarine. +-- @param #POSITIONABLE self +-- @return #boolean Submarines attributes result. +function POSITIONABLE:IsSubmarine() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + if UnitDescriptor.attributes["Submarines"] == true then + return true + else + return false + end + end + + return nil +end From dab486ec99391b6364fda3f0bcd3ceb118b3cc48 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 09:16:26 +0200 Subject: [PATCH 5/6] Update Controllable.lua Typo in `OptionDisperseOnAttack()` --- Moose Development/Moose/Wrapper/Controllable.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 58e1a747e..5100f51a2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1832,7 +1832,7 @@ end do -- Patrol methods - --- (GROUND) Patrol iteratively using the waypoints the for the (parent) group. + --- (GROUND) Patrol iteratively using the waypoints of the (parent) group. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE function CONTROLLABLE:PatrolRoute() @@ -3817,7 +3817,7 @@ function CONTROLLABLE:OptionDisperseOnAttack(Seconds) local Controller = self:_GetController() if Controller then if self:IsGround() then - self:SetOption(AI.Option.GROUND.id.DISPERSE_ON_ATTACK, seconds) + self:SetOption(AI.Option.Ground.id.DISPERSE_ON_ATTACK, seconds) end end return self From cb14961dcd88726816ee4b826a1b5e6db692e418 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 26 May 2021 15:54:20 +0200 Subject: [PATCH 6/6] Update Zone.lua (#1541) added function `ZONE_POLYGON_BASE:Boundary()` --- Moose Development/Moose/Core/Zone.lua | 40 +++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 70d087b4b..65ac3cb9d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1576,6 +1576,7 @@ end -- ## Draw zone -- -- * @{#ZONE_POLYGON_BASE.DrawZone}(): Draws the zone on the F10 map. +-- * @{#ZONE_POLYGON_BASE.Boundary}(): Draw a frontier on the F10 map with small filled circles. -- -- -- @field #ZONE_POLYGON_BASE @@ -2025,6 +2026,45 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } 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. +-- @param #table Color (Optional) RGB color table {r, g, b}, e.g. {1, 0, 0} for red. Default {1, 1, 1}= White. +-- @param #number Radius (Optional) Radius of the circles in meters. Default 1000. +-- @param #number Alpha (Optional) Alpha transparency [0,1]. Default 1. +-- @param #number Segments (Optional) Number of segments within boundary line. Default 10. +-- @param #boolean Closed (Optional) Link the last point with the first one to obtain a closed boundary. Default false +-- @return #ZONE_POLYGON_BASE self +function ZONE_POLYGON_BASE:Boundary(Coalition, Color, Radius, Alpha, Segments, Closed) + Coalition = Coalition or -1 + Color = Color or {1, 1, 1} + Radius = Radius or 1000 + Alpha = Alpha or 1 + Segments = Segments or 10 + Closed = Closed or false + local i = 1 + local j = #self._.Polygon + if (Closed) then + Limit = #self._.Polygon + 1 + else + Limit = #self._.Polygon + end + while i <= #self._.Polygon do + self:T( { i, j, self._.Polygon[i], self._.Polygon[j] } ) + if j ~= Limit then + local DeltaX = self._.Polygon[j].x - self._.Polygon[i].x + local DeltaY = self._.Polygon[j].y - self._.Polygon[i].y + for Segment = 0, Segments do + local PointX = self._.Polygon[i].x + ( Segment * DeltaX / Segments ) + local PointY = self._.Polygon[i].y + ( Segment * DeltaY / Segments ) + ZONE_RADIUS:New( "Zone", {x = PointX, y = PointY}, Radius ):DrawZone(Coalition, Color, 1, Color, Alpha, nil, true) + end + end + j = i + i = i + 1 + end + return self +end --- @type ZONE_POLYGON -- @extends #ZONE_POLYGON_BASE