--- **Wrapper** - POSITIONABLE wraps DCS classes that are "positionable". -- -- === -- -- ### Author: **FlightControl** -- -- ### Contributions: **Hardcard**, **funkyfranky** -- -- === -- -- @module Wrapper.Positionable -- @image Wrapper_Positionable.JPG --- @type POSITIONABLE.__ Methods which are not intended for mission designers, but which are used internally by the moose designer :-) -- @extends Wrapper.Identifiable#IDENTIFIABLE --- @type POSITIONABLE -- @field Core.Point#COORDINATE coordinate Coordinate object. -- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object. -- @extends Wrapper.Identifiable#IDENTIFIABLE --- Wrapper class to handle the POSITIONABLE objects. -- -- * Support all DCS APIs. -- * Enhance with POSITIONABLE specific APIs not in the DCS API set. -- * Manage the "state" of the POSITIONABLE. -- -- ## POSITIONABLE constructor -- -- The POSITIONABLE class provides the following functions to construct a POSITIONABLE instance: -- -- * @{#POSITIONABLE.New}(): Create a POSITIONABLE instance. -- -- ## Get the current speed -- -- There are 3 methods that can be used to determine the speed. -- Use @{#POSITIONABLE.GetVelocityKMH}() to retrieve the current speed in km/h. Use @{#POSITIONABLE.GetVelocityMPS}() to retrieve the speed in meters per second. -- The method @{#POSITIONABLE.GetVelocity}() returns the speed vector (a Vec3). -- -- ## Get the current altitude -- -- Altitude can be retrieved using the method @{#POSITIONABLE.GetHeight}() and returns the current altitude in meters from the orthonormal plane. -- -- -- @field #POSITIONABLE POSITIONABLE = { ClassName = "POSITIONABLE", PositionableName = "", coordinate = nil, pointvec3 = nil, } --- @field #POSITIONABLE.__ POSITIONABLE.__ = {} --- @field #POSITIONABLE.__.Cargo POSITIONABLE.__.Cargo = {} --- A DCSPositionable -- @type DCSPositionable -- @field id_ The ID of the controllable in DCS --- Create a new POSITIONABLE from a DCSPositionable -- @param #POSITIONABLE self -- @param #string PositionableName The POSITIONABLE name -- @return #POSITIONABLE self function POSITIONABLE:New( PositionableName ) local self = BASE:Inherit( self, IDENTIFIABLE:New( PositionableName ) ) -- #POSITIONABLE self.PositionableName = PositionableName return self end --- Destroys the POSITIONABLE. -- @param #POSITIONABLE self -- @param #boolean GenerateEvent (Optional) If true, generates a crash or dead event for the unit. If false, no event generated. If nil, a remove event is generated. -- @return #nil The DCS Unit is not existing or alive. -- @usage -- -- Air unit example: destroy the Helicopter and generate a S_EVENT_CRASH for each unit in the Helicopter group. -- Helicopter = UNIT:FindByName( "Helicopter" ) -- Helicopter:Destroy( true ) -- -- @usage -- -- Ground unit example: destroy the Tanks and generate a S_EVENT_DEAD for each unit in the Tanks group. -- Tanks = UNIT:FindByName( "Tanks" ) -- Tanks:Destroy( true ) -- -- @usage -- -- Ship unit example: destroy the Ship silently. -- Ship = STATIC:FindByName( "Ship" ) -- Ship:Destroy() -- -- @usage -- -- Destroy without event generation example. -- Ship = STATIC:FindByName( "Boat" ) -- Ship:Destroy( false ) -- Don't generate an event upon destruction. -- function POSITIONABLE:Destroy( GenerateEvent ) self:F2( self.ObjectName ) local DCSObject = self:GetDCSObject() if DCSObject then local UnitGroup = self:GetGroup() local UnitGroupName = UnitGroup:GetName() self:F( { UnitGroupName = UnitGroupName } ) if GenerateEvent and GenerateEvent == true then if self:IsAir() then self:CreateEventCrash( timer.getTime(), DCSObject ) else self:CreateEventDead( timer.getTime(), DCSObject ) end elseif GenerateEvent == false then -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), DCSObject ) end USERFLAG:New( UnitGroupName ):Set( 100 ) DCSObject:destroy() end return nil end --- Returns the DCS object. Polymorphic for other classes like UNIT, STATIC, GROUP, AIRBASE. -- @param #POSITIONABLE self -- @return DCS#Object The DCS object. function POSITIONABLE:GetDCSObject() return nil end --- Returns a pos3 table of the objects current position and orientation in 3D space. X, Y, Z values are unit vectors defining the objects orientation. -- Coordinates are dependent on the position of the maps origin. -- @param #POSITIONABLE self -- @return DCS#Position3 Table consisting of the point and orientation tables. function POSITIONABLE:GetPosition() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() self:T3( PositionablePosition ) return PositionablePosition end BASE:E( { "Cannot GetPosition", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns a {@DCS#Vec3} table of the objects current orientation in 3D space. X, Y, Z values are unit vectors defining the objects orientation. -- X is the orientation parallel to the movement of the object, Z perpendicular and Y vertical orientation. -- @param #POSITIONABLE self -- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement. -- @return DCS#Vec3 Y orientation, i.e. vertical. -- @return DCS#Vec3 Z orientation, i.e. perpendicular to the direction of movement. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientation() local position = self:GetPosition() if position then return position.x, position.y, position.z else BASE:E( { "Cannot GetOrientation", Positionable = self, Alive = self:IsAlive() } ) return nil, nil, nil end end --- Returns a {@DCS#Vec3} table of the objects current X orientation in 3D space, i.e. along the direction of movement. -- @param #POSITIONABLE self -- @return DCS#Vec3 X orientation, i.e. parallel to the direction of movement. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationX() local position = self:GetPosition() if position then return position.x else BASE:E( { "Cannot GetOrientationX", Positionable = self, Alive = self:IsAlive() } ) return nil end end --- Returns a {@DCS#Vec3} table of the objects current Y orientation in 3D space, i.e. vertical orientation. -- @param #POSITIONABLE self -- @return DCS#Vec3 Y orientation, i.e. vertical. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationY() local position = self:GetPosition() if position then return position.y else BASE:E( { "Cannot GetOrientationY", Positionable = self, Alive = self:IsAlive() } ) return nil end end --- Returns a {@DCS#Vec3} table of the objects current Z orientation in 3D space, i.e. perpendicular to direction of movement. -- @param #POSITIONABLE self -- @return DCS#Vec3 Z orientation, i.e. perpendicular to movement. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetOrientationZ() local position = self:GetPosition() if position then return position.z else BASE:E( { "Cannot GetOrientationZ", Positionable = self, Alive = self:IsAlive() } ) return nil end end --- Returns the @{DCS#Position3} position vectors indicating the point and direction vectors in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @return DCS#Position The 3D position vectors of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPositionVec3() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition().p self:T3( PositionablePosition ) return PositionablePosition end BASE:E( { "Cannot GetPositionVec3", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns the @{DCS#Vec3} vector indicating the 3D vector of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then --local status, vec3 = pcall( -- function() -- local vec3 = DCSPositionable:getPoint() -- return vec3 --end --) local vec3 = DCSPositionable:getPoint() --if status then return vec3 --else --self:E( { "Cannot get Vec3 from DCS Object", Positionable = self, Alive = self:IsAlive() } ) --end end -- ERROR! self:E( { "Cannot get the Positionable DCS Object for GetVec3", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns the @{DCS#Vec2} vector indicating the point in 2D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @return DCS#Vec2 The 2D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVec2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then local Vec3 = DCSPositionable:getPoint() -- DCS#Vec3 return { x = Vec3.x, y = Vec3.z } end self:E( { "Cannot GetVec2", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec2() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionableVec3 = DCSPositionable:getPosition().p local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 ) -- self:F( PositionablePointVec2 ) return PositionablePointVec2 end self:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPointVec3() local DCSPositionable = self:GetDCSObject() if DCSPositionable then -- Get 3D vector. local PositionableVec3 = self:GetPositionVec3() if false and self.pointvec3 then -- Update vector. self.pointvec3.x = PositionableVec3.x self.pointvec3.y = PositionableVec3.y self.pointvec3.z = PositionableVec3.z else -- Create a new POINT_VEC3 object. self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 ) end return self.pointvec3 end BASE:E( { "Cannot GetPointVec3", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns a reference to a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- This function works similar to POSITIONABLE.GetCoordinate(), however, this function caches, updates and re-uses the same COORDINATE object stored -- within the POSITIONABLE. This has higher performance, but comes with all considerations associated with the possible referencing to the same COORDINATE object. -- This should only be used when performance is critical and there is sufficient awareness of the possible pitfalls. However, in most instances, GetCoordinate() is -- preferred as it will return a fresh new COORDINATE and thus avoid potentially unexpected issues. -- @param #POSITIONABLE self -- @return Core.Point#COORDINATE A reference to the COORDINATE object of the POSITIONABLE. function POSITIONABLE:GetCoord() -- Get DCS object. local DCSPositionable = self:GetDCSObject() if DCSPositionable then -- Get the current position. local PositionableVec3 = self:GetVec3() if self.coordinate then -- Update COORDINATE from 3D vector. self.coordinate:UpdateFromVec3( PositionableVec3 ) else -- New COORDINATE. self.coordinate = COORDINATE:NewFromVec3( PositionableVec3 ) end return self.coordinate end -- Error message. BASE:E( { "Cannot GetCoordinate", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns a new COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @return Core.Point#COORDINATE A new COORDINATE object of the POSITIONABLE. function POSITIONABLE:GetCoordinate() -- Get DCS object. local DCSPositionable = self:GetDCSObject() if DCSPositionable then -- Get the current position. local PositionableVec3 = self:GetVec3() local coord=COORDINATE:NewFromVec3(PositionableVec3) local heading = self:GetHeading() coord.Heading = heading -- Return a new coordiante object. return coord end -- Error message. self:E( { "Cannot GetCoordinate", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Triggers an explosion at the coordinates of the positionable. -- @param #POSITIONABLE self -- @param #number power Power of the explosion in kg TNT. Default 100 kg TNT. -- @param #number delay (Optional) Delay of explosion in seconds. -- @return #POSITIONABLE self function POSITIONABLE:Explode(power, delay) -- Default. power=power or 100 -- Check if delay or not. if delay and delay>0 then -- Delayed call. self:ScheduleOnce(delay, POSITIONABLE.Explode, self, power, 0) else local coord=self:GetCoord() if coord then -- Create an explotion at the coordinate of the positionable. coord:Explosion(power) end end return self end --- Returns a COORDINATE object, which is offset with respect to the orientation of the POSITIONABLE. -- @param #POSITIONABLE self -- @param #number x Offset in the direction "the nose" of the unit is pointing in meters. Default 0 m. -- @param #number y Offset "above" the unit in meters. Default 0 m. -- @param #number z Offset in the direction "the wing" of the unit is pointing in meters. z>0 starboard, z<0 port. Default 0 m. -- @return Core.Point#COORDINATE The COORDINATE of the offset with respect to the orientation of the POSITIONABLE. function POSITIONABLE:GetOffsetCoordinate( x, y, z ) -- Default if nil. x = x or 0 y = y or 0 z = z or 0 -- Vectors making up the coordinate system. local X = self:GetOrientationX() local Y = self:GetOrientationY() local Z = self:GetOrientationZ() -- Offset vector: x meters ahead, z meters starboard, y meters above. local A = { x = x, y = y, z = z } -- Scale components of orthonormal coordinate vectors. local x = { x = X.x * A.x, y = X.y * A.x, z = X.z * A.x } local y = { x = Y.x * A.y, y = Y.y * A.y, z = Y.z * A.y } local z = { x = Z.x * A.z, y = Z.y * A.z, z = Z.z * A.z } -- Add up vectors in the unit coordinate system ==> this gives the offset vector relative the the origin of the map. local a = { x = x.x + y.x + z.x, y = x.y + y.y + z.y, z = x.z + y.z + z.z } -- Vector from the origin of the map to the unit. local u = self:GetVec3() -- Translate offset vector from map origin to the unit: v=u+a. local v = { x = a.x + u.x, y = a.y + u.y, z = a.z + u.z } local coord = COORDINATE:NewFromVec3( v ) -- Return the offset coordinate. return coord end --- Returns a COORDINATE object, which is transformed to be relative to the POSITIONABLE. Inverse of @{#POSITIONABLE.GetOffsetCoordinate}. -- @param #POSITIONABLE self -- @param #number x Offset along the world x-axis in meters. Default 0 m. -- @param #number y Offset along the world y-axis in meters. Default 0 m. -- @param #number z Offset along the world z-axis in meters. Default 0 m. -- @return Core.Point#COORDINATE The relative COORDINATE with respect to the orientation of the POSITIONABLE. function POSITIONABLE:GetRelativeCoordinate( x, y, z ) -- Default if nil. x = x or 0 y = y or 0 z = z or 0 -- Vector from the origin of the map to self. local selfPos = self:GetVec3() -- Vectors making up self's local coordinate system. local X = self:GetOrientationX() local Y = self:GetOrientationY() local Z = self:GetOrientationZ() -- Offset from self to self's position (still in the world coordinate system). local off = { x = x - selfPos.x, y = y - selfPos.y, z = z - selfPos.z } -- The end result local res = { x = 0, y = 0, z = 0 } -- Matrix equation to solve: -- | X.x, Y.x, Z.x | | res.x | | off.x | -- | X.y, Y.y, Z.y | . | res.y | = | off.y | -- | X.z, Y.z, Z.z | | res.z | | off.z | -- Use gaussian elimination to solve the system of equations. -- https://en.wikipedia.org/wiki/Gaussian_elimination local mat = { { X.x, Y.x, Z.x, off.x }, { X.y, Y.y, Z.y, off.y }, { X.z, Y.z, Z.z, off.z } } -- Matrix dimensions local m = 3 local n = 4 -- Pivot indices local h = 1 local k = 1 while h <= m and k <= n do local v_max = math.abs( mat[h][k] ) local i_max = h for i = h,m,1 do local value = math.abs( mat[i][k] ) if value > v_max then i_max = i v_max = value end end if mat[i_max][k] == 0 then -- Already all 0s, nothing to do. k = k + 1 else -- Swap rows h and i_max local tmp = mat[h] mat[h] = mat[i_max] mat[i_max] = tmp for i = h + 1,m,1 do -- The scaling factor to use to reduce all values in this column local f = mat[i][k] / mat[h][k] mat[i][k] = 0 for j = k+1,n,1 do mat[i][j] = mat[i][j] - f*mat[h][j] end end h = h + 1 k = k + 1 end end -- mat is now in row echelon form: -- | #, #, #, # | -- | 0, #, #, # | -- | 0, 0, #, # | -- -- and the linear equation is now effectively: -- | #, #, # | | res.x | | # | -- | 0, #, # | . | res.y | = | # | -- | 0, 0, # | | res.z | | # | -- this resulting system of equations can be easily solved via substitution. res.z = mat[3][4] / mat[3][3] res.y = (mat[2][4] - res.z * mat[2][3]) / mat[2][2] res.x = (mat[1][4] - res.y * mat[1][2] - res.z * mat[1][3]) / mat[1][1] local coord = COORDINATE:NewFromVec3( res ) -- Return the relative coordinate. return coord end --- Returns a random @{DCS#Vec3} vector within a range, indicating the point in 3D of the POSITIONABLE within the mission. -- @param #POSITIONABLE self -- @param #number Radius -- @return DCS#Vec3 The 3D point vector of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. -- @usage -- -- If Radius is ignored, returns the DCS#Vec3 of first UNIT of the GROUP function POSITIONABLE:GetRandomVec3( Radius ) self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionablePointVec3 = DCSPositionable:getPosition().p if Radius then local PositionableRandomVec3 = {} local angle = math.random() * math.pi * 2 PositionableRandomVec3.x = PositionablePointVec3.x + math.cos( angle ) * math.random() * Radius PositionableRandomVec3.y = PositionablePointVec3.y PositionableRandomVec3.z = PositionablePointVec3.z + math.sin( angle ) * math.random() * Radius self:T3( PositionableRandomVec3 ) return PositionableRandomVec3 else self:F( "Radius is nil, returning the PointVec3 of the POSITIONABLE", PositionablePointVec3 ) return PositionablePointVec3 end end BASE:E( { "Cannot GetRandomVec3", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self -- @return DCS#Box3 The bounding box of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetBoundingBox() self:F2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionableDesc = DCSPositionable:getDesc() -- DCS#Desc if PositionableDesc then local PositionableBox = PositionableDesc.box return PositionableBox end end BASE:E( { "Cannot GetBoundingBox", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Get the object size. -- @param #POSITIONABLE self -- @return DCS#Distance Max size of object in x, z or 0 if bounding box could not be obtained. -- @return DCS#Distance Length x or 0 if bounding box could not be obtained. -- @return DCS#Distance Height y or 0 if bounding box could not be obtained. -- @return DCS#Distance Width z or 0 if bounding box could not be obtained. function POSITIONABLE:GetObjectSize() -- Get bounding box. local box = self:GetBoundingBox() if box then local x = box.max.x + math.abs( box.min.x ) -- length local y = box.max.y + math.abs( box.min.y ) -- height local z = box.max.z + math.abs( box.min.z ) -- width return math.max( x, z ), x, y, z end return 0, 0, 0, 0 end --- Get the bounding radius of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self -- @param #number MinDist (Optional) If bounding box is smaller than this value, MinDist is returned. -- @return DCS#Distance The bounding radius of the POSITIONABLE -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetBoundingRadius( MinDist ) self:F2() local Box = self:GetBoundingBox() local boxmin = MinDist or 0 if Box then local X = Box.max.x - Box.min.x local Z = Box.max.z - Box.min.z local CX = X / 2 local CZ = Z / 2 return math.max( math.max( CX, CZ ), boxmin ) end BASE:E( { "Cannot GetBoundingRadius", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns the altitude above sea level of the POSITIONABLE. -- @param #POSITIONABLE self -- @return DCS#Distance The altitude of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAltitude() self:F2() local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionablePointVec3 = DCSPositionable:getPoint() -- DCS#Vec3 return PositionablePointVec3.y end BASE:E( { "Cannot GetAltitude", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns if the Positionable is located above a runway. -- @param #POSITIONABLE self -- @return #boolean true if Positionable is above a runway. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsAboveRunway() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then local Vec2 = self:GetVec2() local SurfaceType = land.getSurfaceType( Vec2 ) local IsAboveRunway = SurfaceType == land.SurfaceType.RUNWAY self:T2( IsAboveRunway ) return IsAboveRunway end BASE:E( { "Cannot IsAboveRunway", Positionable = self, Alive = self:IsAlive() } ) return nil end function POSITIONABLE:GetSize() local DCSObject = self:GetDCSObject() if DCSObject then return 1 else return 0 end end --- Returns the POSITIONABLE heading in degrees. -- @param #POSITIONABLE self -- @return #number The POSITIONABLE heading in degrees. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetHeading() local DCSPositionable = self:GetDCSObject() if DCSPositionable then local PositionablePosition = DCSPositionable:getPosition() if PositionablePosition then local PositionableHeading = math.atan2( PositionablePosition.x.z, PositionablePosition.x.x ) if PositionableHeading < 0 then PositionableHeading = PositionableHeading + 2 * math.pi end PositionableHeading = PositionableHeading * 180 / math.pi return PositionableHeading end end self:E( { "Cannot GetHeading", Positionable = self, Alive = self:IsAlive() } ) return nil end -- Is Methods --- Returns if the unit is of an air category. -- If the unit is a helicopter or a plane, then this method will return true, otherwise false. -- @param #POSITIONABLE self -- @return #boolean Air category evaluation result. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsAir() self:F2() local DCSUnit = self:GetDCSObject() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) self:T3( IsAirResult ) return IsAirResult end self:E( { "Cannot check IsAir", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns if the unit is of an ground category. -- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. -- @param #POSITIONABLE self -- @return #boolean Ground category evaluation result. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsGround() self:F2() local DCSUnit = self:GetDCSObject() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) local IsGroundResult = (UnitDescriptor.category == Unit.Category.GROUND_UNIT) self:T3( IsGroundResult ) return IsGroundResult end self:E( { "Cannot check IsGround", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns if the unit is of ship category. -- @param #POSITIONABLE self -- @return #boolean Ship category evaluation result. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:IsShip() self:F2() local DCSUnit = self:GetDCSObject() if DCSUnit then local UnitDescriptor = DCSUnit:getDesc() self:T3( { UnitDescriptor.category, Unit.Category.SHIP } ) local IsShipResult = (UnitDescriptor.category == Unit.Category.SHIP) self:T3( IsShipResult ) return IsShipResult end self:E( { "Cannot check IsShip", Positionable = self, Alive = self:IsAlive() } ) 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 self:E( { "Cannot check IsSubmarine", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. -- @param #POSITIONABLE self -- @return #boolean true if in the air. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:InAir() self:F2( self.PositionableName ) return nil end --- Returns the @{Core.Velocity} object from the POSITIONABLE. -- @param #POSITIONABLE self -- @return Core.Velocity#VELOCITY Velocity The Velocity object. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocity() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable then local Velocity = VELOCITY:New( self ) return Velocity end BASE:E( { "Cannot GetVelocity", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Returns the POSITIONABLE velocity Vec3 vector. -- @param #POSITIONABLE self -- @return DCS#Vec3 The velocity Vec3 vector -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetVelocityVec3() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable and DCSPositionable:isExist() then local PositionableVelocityVec3 = DCSPositionable:getVelocity() self:T3( PositionableVelocityVec3 ) return PositionableVelocityVec3 end BASE:E( { "Cannot GetVelocityVec3", Positionable = self, Alive = self:IsAlive() } ) return nil end --- Get relative velocity with respect to another POSITIONABLE. -- @param #POSITIONABLE self -- @param #POSITIONABLE Positionable Other POSITIONABLE. -- @return #number Relative velocity in m/s. function POSITIONABLE:GetRelativeVelocity( Positionable ) self:F2( self.PositionableName ) local v1 = self:GetVelocityVec3() local v2 = Positionable:GetVelocityVec3() local vtot = UTILS.VecAdd( v1, v2 ) return UTILS.VecNorm( vtot ) end --- Returns the POSITIONABLE height above sea level in meters. -- @param #POSITIONABLE self -- @return DCS#Vec3 Height of the positionable in meters (or nil, if the object does not exist). function POSITIONABLE:GetHeight() --R2.1 self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable and DCSPositionable:isExist() then local PositionablePosition = DCSPositionable:getPosition() if PositionablePosition then local PositionableHeight = PositionablePosition.p.y self:T2( PositionableHeight ) return PositionableHeight end end return nil end --- Returns the POSITIONABLE velocity in km/h. -- @param #POSITIONABLE self -- @return #number The velocity in km/h. function POSITIONABLE:GetVelocityKMH() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable and DCSPositionable:isExist() then local VelocityVec3 = self:GetVelocityVec3() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec local Velocity = Velocity * 3.6 -- now it is in km/h. self:T3( Velocity ) return Velocity end return 0 end --- Returns the POSITIONABLE velocity in meters per second. -- @param #POSITIONABLE self -- @return #number The velocity in meters per second. function POSITIONABLE:GetVelocityMPS() self:F2( self.PositionableName ) local DCSPositionable = self:GetDCSObject() if DCSPositionable and DCSPositionable:isExist() then local VelocityVec3 = self:GetVelocityVec3() local Velocity = ( VelocityVec3.x ^ 2 + VelocityVec3.y ^ 2 + VelocityVec3.z ^ 2 ) ^ 0.5 -- in meters / sec self:T3( Velocity ) return Velocity end return 0 end --- Returns the POSITIONABLE velocity in knots. -- @param #POSITIONABLE self -- @return #number The velocity in knots. function POSITIONABLE:GetVelocityKNOTS() self:F2( self.PositionableName ) return UTILS.MpsToKnots( self:GetVelocityMPS() ) end --- Returns the true airspeed (TAS). This is calculated from the current velocity minus wind in 3D. -- @param #POSITIONABLE self -- @return #number TAS in m/s. Returns 0 if the POSITIONABLE does not exist. function POSITIONABLE:GetAirspeedTrue() -- TAS local tas=0 -- Get current coordinate. local coord=self:GetCoord() if coord then -- Altitude in meters. local alt=coord.y -- Wind velocity vector. local wvec3=coord:GetWindVec3(alt, false) -- Velocity vector. local vvec3=self:GetVelocityVec3() --GS=TAS+WIND ==> TAS=GS-WIND local tasvec3=UTILS.VecSubstract(vvec3, wvec3) -- True airspeed in m/s tas=UTILS.VecNorm(tasvec3) end return tas end --- Returns the indicated airspeed (IAS). -- The IAS is calculated from the TAS under the approximation that TAS increases by ~2% with every 1000 feet altitude ASL. -- @param #POSITIONABLE self -- @param #number oatcorr (Optional) Outside air temperature (OAT) correction factor. Default 0.017 (=1.7%). -- @return #number IAS in m/s. Returns 0 if the POSITIONABLE does not exist. function POSITIONABLE:GetAirspeedIndicated(oatcorr) -- Get true airspeed. local tas=self:GetAirspeedTrue() -- Get altitude. local altitude=self:GetAltitude() -- Convert TAS to IAS. local ias=UTILS.TasToIas(tas, altitude, oatcorr) return ias end --- Returns the horizonal speed relative to eath's surface. The vertical component of the velocity vector is projected out (set to zero). -- @param #POSITIONABLE self -- @return #number Ground speed in m/s. Returns 0 if the POSITIONABLE does not exist. function POSITIONABLE:GetGroundSpeed() local gs=0 local vel=self:GetVelocityVec3() if vel then local vec2={x=vel.x, y=vel.z} gs=UTILS.Vec2Norm(vel) end return gs end --- Returns the Angle of Attack of a POSITIONABLE. -- @param #POSITIONABLE self -- @return #number Angle of attack in degrees. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetAoA() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then -- Get velocity vector of the unit. local unitvel = self:GetVelocityVec3() if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- Get wind vector including turbulences. local wind = self:GetCoordinate():GetWindWithTurbulenceVec3() -- Include wind vector. unitvel.x = unitvel.x - wind.x unitvel.y = unitvel.y - wind.y unitvel.z = unitvel.z - wind.z -- Unit velocity transformed into aircraft axes directions. local AxialVel = {} -- Transform velocity components in direction of aircraft axes. AxialVel.x = UTILS.VecDot( unitpos.x, unitvel ) AxialVel.y = UTILS.VecDot( unitpos.y, unitvel ) AxialVel.z = UTILS.VecDot( unitpos.z, unitvel ) -- AoA is angle between unitpos.x and the x and y velocities. local AoA = math.acos( UTILS.VecDot( { x = 1, y = 0, z = 0 }, { x = AxialVel.x, y = AxialVel.y, z = 0 } ) / UTILS.VecNorm( { x = AxialVel.x, y = AxialVel.y, z = 0 } ) ) -- Set correct direction: if AxialVel.y > 0 then AoA = -AoA end -- Return AoA value in degrees. return math.deg( AoA ) end end return nil end --- Returns the climb or descent angle of the POSITIONABLE. -- @param #POSITIONABLE self -- @return #number Climb or descent angle in degrees. Or 0 if velocity vector norm is zero. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetClimbAngle() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then -- Get velocity vector of the unit. local unitvel = self:GetVelocityVec3() if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- Calculate climb angle. local angle = math.asin( unitvel.y / UTILS.VecNorm( unitvel ) ) -- Return angle in degrees. return math.deg( angle ) else return 0 end end return nil end --- Returns the pitch angle of a POSITIONABLE. -- @param #POSITIONABLE self -- @return #number Pitch angle in degrees. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetPitch() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then return math.deg( math.asin( unitpos.x.y ) ) end return nil end --- Returns the roll angle of a unit. -- @param #POSITIONABLE self -- @return #number Pitch angle in degrees. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetRoll() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then -- First, make a vector that is perpendicular to y and unitpos.x with cross product local cp = UTILS.VecCross( unitpos.x, { x = 0, y = 1, z = 0 } ) -- Now, get dot product of of this cross product with unitpos.z local dp = UTILS.VecDot( cp, unitpos.z ) -- Now get the magnitude of the roll (magnitude of the angle between two vectors is acos(vec1.vec2/|vec1||vec2|) local Roll = math.acos( dp / (UTILS.VecNorm( cp ) * UTILS.VecNorm( unitpos.z )) ) -- Now, have to get sign of roll. By convention, making right roll positive -- To get sign of roll, use the y component of unitpos.z. For right roll, y component is negative. if unitpos.z.y > 0 then -- left roll, flip the sign of the roll Roll = -Roll end return math.deg( Roll ) end return nil end --- Returns the yaw angle of a POSITIONABLE. -- @param #POSITIONABLE self -- @return #number Yaw angle in degrees. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetYaw() -- Get position of the unit. local unitpos = self:GetPosition() if unitpos then -- get unit velocity local unitvel = self:GetVelocityVec3() if unitvel and UTILS.VecNorm( unitvel ) ~= 0 then -- must have non-zero velocity! local AxialVel = {} -- unit velocity transformed into aircraft axes directions -- transform velocity components in direction of aircraft axes. AxialVel.x = UTILS.VecDot( unitpos.x, unitvel ) AxialVel.y = UTILS.VecDot( unitpos.y, unitvel ) AxialVel.z = UTILS.VecDot( unitpos.z, unitvel ) -- Yaw is the angle between unitpos.x and the x and z velocities -- define right yaw as positive local Yaw = math.acos( UTILS.VecDot( { x = 1, y = 0, z = 0 }, { x = AxialVel.x, y = 0, z = AxialVel.z } ) / UTILS.VecNorm( { x = AxialVel.x, y = 0, z = AxialVel.z } ) ) -- now set correct direction: if AxialVel.z > 0 then Yaw = -Yaw end return Yaw end end return nil end --- Returns the message text with the callsign embedded (if there is one). -- @param #POSITIONABLE self -- @param #string Message The message text. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return #string The message text. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetMessageText( Message, Name ) local DCSObject = self:GetDCSObject() if DCSObject then local Callsign = string.format( "%s", ((Name ~= "" and Name) or self:GetCallsign() ~= "" and self:GetCallsign()) or self:GetName() ) local MessageText = string.format( "%s - %s", Callsign, Message ) return MessageText end return nil end --- Returns a message with the callsign embedded (if there is one). -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return Core.Message#MESSAGE function POSITIONABLE:GetMessage( Message, Duration, Name ) local DCSObject = self:GetDCSObject() if DCSObject then local MessageText = self:GetMessageText( Message, Name ) return MESSAGE:New( MessageText, Duration ) end return nil end --- Returns a message of a specified type with the callsign embedded (if there is one). -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE MessageType MessageType The message type. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. -- @return Core.Message#MESSAGE function POSITIONABLE:GetMessageType( Message, MessageType, Name ) -- R2.2 changed callsign and name and using GetMessageText local DCSObject = self:GetDCSObject() if DCSObject then local MessageText = self:GetMessageText( Message, Name ) return MESSAGE:NewType( MessageText, MessageType ) end return nil end --- Send a message to all coalitions. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToAll( Message, Duration, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessage( Message, Duration, Name ):ToAll() end return nil end --- Send a message to a coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param DCS#coalition MessageCoalition The Coalition receiving the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToCoalition( Message, Duration, MessageCoalition, Name ) self:F2( { Message, Duration } ) local Name = Name or "" local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessage( Message, Duration, Name ):ToCoalition( MessageCoalition ) end return nil end --- Send a message to a coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. -- @param DCS#coalition MessageCoalition The Coalition receiving the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageTypeToCoalition( Message, MessageType, MessageCoalition, Name ) self:F2( { Message, MessageType } ) local Name = Name or "" local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessageType( Message, MessageType, Name ):ToCoalition( MessageCoalition ) end return nil end --- Send a message to the red coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToRed( Message, Duration, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessage( Message, Duration, Name ):ToRed() end return nil end --- Send a message to the blue coalition. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToBlue( Message, Duration, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessage( Message, Duration, Name ):ToBlue() end return nil end --- Send a message to a client. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Client#CLIENT Client The client object receiving the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToClient( Message, Duration, Client, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessage( Message, Duration, Name ):ToClient( Client ) end return nil end --- Send a message to a @{Wrapper.Unit}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Unit#UNIT MessageUnit The UNIT object receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToUnit( Message, Duration, MessageUnit, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then if MessageUnit:IsAlive() then self:GetMessage( Message, Duration, Name ):ToUnit( MessageUnit ) else BASE:E( { "Message not sent to Unit; Unit is not alive...", Message = Message, MessageUnit = MessageUnit } ) end else BASE:E( { "Message not sent to Unit; Positionable is not alive ...", Message = Message, Positionable = self, MessageUnit = MessageUnit } ) end end end --- Send a message to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToGroup( Message, Duration, MessageGroup, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then if MessageGroup:IsAlive() then self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) else BASE:E( { "Message not sent to Group; Group is not alive...", Message = Message, MessageGroup = MessageGroup } ) end else BASE:E( { "Message not sent to Group; Positionable is not alive ...", Message = Message, Positionable = self, MessageGroup = MessageGroup } ) end end return nil end --- Send a message to a @{Wrapper.Unit}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Wrapper.Unit#UNIT MessageUnit The UNIT object receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToUnit( Message, Duration, MessageUnit, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then if MessageUnit:IsAlive() then self:GetMessage( Message, Duration, Name ):ToUnit( MessageUnit ) else BASE:E( { "Message not sent to Unit; Unit is not alive...", Message = Message, MessageUnit = MessageUnit } ) end else BASE:E( { "Message not sent to Unit; Positionable is not alive ...", Message = Message, Positionable = self, MessageUnit = MessageUnit } ) end end return nil end --- Send a message of a message type to a @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param Core.Message#MESSAGE.Type MessageType The message type that determines the duration. -- @param Wrapper.Group#GROUP MessageGroup The GROUP object receiving the message. -- @param #string Name (Optional) The Name of the sender. If not provided, the Name is the type of the POSITIONABLE. function POSITIONABLE:MessageTypeToGroup( Message, MessageType, MessageGroup, Name ) self:F2( { Message, MessageType } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then self:GetMessageType( Message, MessageType, Name ):ToGroup( MessageGroup ) end end return nil end --- Send a message to a @{Core.Set#SET_GROUP}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Core.Set#SET_GROUP MessageSetGroup The SET_GROUP collection receiving the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:MessageToSetGroup( Message, Duration, MessageSetGroup, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then MessageSetGroup:ForEachGroupAlive( function( MessageGroup ) self:GetMessage( Message, Duration, Name ):ToGroup( MessageGroup ) end ) end end return nil end --- Send a message to a @{Core.Set#SET_UNIT}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Core.Set#SET_UNIT MessageSetUnit The SET_UNIT collection receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then MessageSetUnit:ForEachUnit( function( MessageGroup ) self:GetMessage( Message, Duration, Name ):ToUnit( MessageGroup ) end ) end end return nil end --- Send a message to a @{Core.Set#SET_UNIT}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param Core.Set#SET_UNIT MessageSetUnit The SET_UNIT collection receiving the message. -- @param #string Name (optional) The Name of the sender. If not provided, the Name is the type of the Positionable. function POSITIONABLE:MessageToSetUnit( Message, Duration, MessageSetUnit, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then if DCSObject:isExist() then MessageSetUnit:ForEachUnit( function( MessageGroup ) self:GetMessage( Message, Duration, Name ):ToUnit( MessageGroup ) end ) end end return nil end --- Send a message to the players in the @{Wrapper.Group}. -- The message will appear in the message area. The message will begin with the callsign of the group and the type of the first unit sending the message. -- @param #POSITIONABLE self -- @param #string Message The message text -- @param DCS#Duration Duration The duration of the message. -- @param #string Name (Optional) The Name of the sender. If not provided, Name is set to the type of the POSITIONABLE. function POSITIONABLE:Message( Message, Duration, Name ) self:F2( { Message, Duration } ) local DCSObject = self:GetDCSObject() if DCSObject then self:GetMessage( Message, Duration, Name ):ToGroup( self ) end return nil end --- Create a @{Sound.Radio#RADIO}, to allow radio transmission for this POSITIONABLE. -- Set parameters with the methods provided, then use RADIO:Broadcast() to actually broadcast the message -- @param #POSITIONABLE self -- @return Sound.Radio#RADIO Radio function POSITIONABLE:GetRadio() self:F2( self ) return RADIO:New( self ) end --- Create a @{Core.Beacon#BEACON}, to allow this POSITIONABLE to broadcast beacon signals. -- @param #POSITIONABLE self -- @return Core.Beacon#BEACON Beacon function POSITIONABLE:GetBeacon() self:F2( self ) return BEACON:New( self ) end --- Start Lasing a POSITIONABLE. -- @param #POSITIONABLE self -- @param #POSITIONABLE Target The target to lase. -- @param #number LaserCode Laser code or random number in [1000, 9999]. -- @param #number Duration Duration of lasing in seconds. -- @return Core.Spot#SPOT function POSITIONABLE:LaseUnit( Target, LaserCode, Duration ) self:F2() LaserCode = LaserCode or math.random( 1000, 9999 ) local RecceDcsUnit = self:GetDCSObject() local TargetVec3 = Target:GetVec3() self:F( "building spot" ) self.Spot = SPOT:New( self ) -- Core.Spot#SPOT self.Spot:LaseOn( Target, LaserCode, Duration ) self.LaserCode = LaserCode return self.Spot end --- Start Lasing a COORDINATE. -- @param #POSITIONABLE self -- @param Core.Point#COORDINATE Coordinate The coordinate where the lase is pointing at. -- @param #number LaserCode Laser code or random number in [1000, 9999]. -- @param #number Duration Duration of lasing in seconds. -- @return Core.Spot#SPOT function POSITIONABLE:LaseCoordinate( Coordinate, LaserCode, Duration ) self:F2() LaserCode = LaserCode or math.random( 1000, 9999 ) self.Spot = SPOT:New( self ) -- Core.Spot#SPOT self.Spot:LaseOnCoordinate( Coordinate, LaserCode, Duration ) self.LaserCode = LaserCode return self.Spot end --- Stop Lasing a POSITIONABLE. -- @param #POSITIONABLE self -- @return #POSITIONABLE function POSITIONABLE:LaseOff() self:F2() if self.Spot then self.Spot:LaseOff() self.Spot = nil end return self end --- Check if the POSITIONABLE is lasing a target. -- @param #POSITIONABLE self -- @return #boolean true if it is lasing a target function POSITIONABLE:IsLasing() self:F2() local Lasing = false if self.Spot then Lasing = self.Spot:IsLasing() end return Lasing end --- Get the Spot -- @param #POSITIONABLE self -- @return Core.Spot#SPOT The Spot function POSITIONABLE:GetSpot() return self.Spot end --- Get the last assigned laser code -- @param #POSITIONABLE self -- @return #number The laser code function POSITIONABLE:GetLaserCode() return self.LaserCode end do -- Cargo --- Add cargo. -- @param #POSITIONABLE self -- @param Core.Cargo#CARGO Cargo -- @return #POSITIONABLE function POSITIONABLE:AddCargo( Cargo ) self.__.Cargo[Cargo] = Cargo return self end --- Get all contained cargo. -- @param #POSITIONABLE self -- @return #POSITIONABLE function POSITIONABLE:GetCargo() return self.__.Cargo end --- Remove cargo. -- @param #POSITIONABLE self -- @param Core.Cargo#CARGO Cargo -- @return #POSITIONABLE function POSITIONABLE:RemoveCargo( Cargo ) self.__.Cargo[Cargo] = nil return self end --- Returns if carrier has given cargo. -- @param #POSITIONABLE self -- @return Core.Cargo#CARGO Cargo function POSITIONABLE:HasCargo( Cargo ) return self.__.Cargo[Cargo] end --- Clear all cargo. -- @param #POSITIONABLE self function POSITIONABLE:ClearCargo() self.__.Cargo = {} end --- Is cargo bay empty. -- @param #POSITIONABLE self function POSITIONABLE:IsCargoEmpty() local IsEmpty = true for _, Cargo in pairs( self.__.Cargo ) do IsEmpty = false break end return IsEmpty end --- Get cargo item count. -- @param #POSITIONABLE self -- @return Core.Cargo#CARGO Cargo function POSITIONABLE:CargoItemCount() local ItemCount = 0 for CargoName, Cargo in pairs( self.__.Cargo ) do ItemCount = ItemCount + Cargo:GetCount() end return ItemCount end --- Get the number of infantry soldiers that can be embarked into an aircraft (airplane or helicopter). -- Returns `nil` for ground or ship units. -- @param #POSITIONABLE self -- @return #number Descent number of soldiers that fit into the unit. Returns `#nil` for ground and ship units. function POSITIONABLE:GetTroopCapacity() local DCSunit=self:GetDCSObject() --DCS#Unit local capacity=DCSunit:getDescentCapacity() return capacity end --- Get Cargo Bay Free Weight in kg. -- @param #POSITIONABLE self -- @return #number CargoBayFreeWeight function POSITIONABLE:GetCargoBayFreeWeight() -- When there is no cargo bay weight limit set, then calculate this for this POSITIONABLE! if not self.__.CargoBayWeightLimit then self:SetCargoBayWeightLimit() end local CargoWeight = 0 for CargoName, Cargo in pairs( self.__.Cargo ) do CargoWeight = CargoWeight + Cargo:GetWeight() end return self.__.CargoBayWeightLimit - CargoWeight end --- -- @field DefaultInfantryWeight -- Abstract out the "XYZ*95" calculation in the Ground POSITIONABLE case, where the table -- holds number of infantry that the vehicle could carry, instead of actual cargo weights. POSITIONABLE.DefaultInfantryWeight = 95 --- -- @field CargoBayCapacityValues -- @field CargoBayCapacityValues.Air -- @field CargoBayCapacityValues.Naval -- @field CargoBayCapacityValues.Ground POSITIONABLE.CargoBayCapacityValues = { ["Air"] = { -- C-17A -- Wiki says: max=265,352, empty=128,140, payload=77,516 (134 troops, 1 M1 Abrams tank, 2 M2 Bradley or 3 Stryker) -- DCS says: max=265,350, empty=125,645, fuel=132,405 ==> Cargo Bay=7300 kg with a full fuel load (lot of fuel!) and 73300 with half a fuel load. --["C-17A"] = 35000, --77519 cannot be used, because it loads way too much apcs and infantry. -- C-130: -- DCS says: max=79,380, empty=36,400, fuel=10,415 kg ==> Cargo Bay=32,565 kg with fuel load. -- Wiki says: max=70,307, empty=34,382, payload=19,000 kg (92 passengers, 2-3 Humvees or 2 M113s), max takeoff weight 70,037 kg. -- Here we say two M113s should be transported. Each one weights 11,253 kg according to DCS. So the cargo weight should be 23,000 kg with a full load of fuel. -- This results in a max takeoff weight of 69,815 kg (23,000+10,415+36,400), which is very close to the Wiki value of 70,037 kg. ["C_130"] = 70000, }, ["Naval"] = { ["Type_071"] = 245000, ["LHA_Tarawa"] = 500000, ["Ropucha_class"] = 150000, ["Dry_cargo_ship_1"] = 70000, ["Dry_cargo_ship_2"] = 70000, ["Higgins_boat"] = 3700, -- Higgins Boat can load 3700 kg of general cargo or 36 men (source wikipedia). ["USS_Samuel_Chase"] = 25000, -- Let's say 25 tons for now. Wiki says 33 Higgins boats, which would be 264 tons (can't be right!) and/or 578 troops. ["LST_Mk2"] = 2100000, -- Can carry 2100 tons according to wiki source! ["speedboat"] = 500, -- 500 kg ~ 5 persons ["Seawise_Giant"] =261000000, -- Gross tonnage is 261,000 tonns. }, ["Ground"] = { ["AAV7"] = 25*POSITIONABLE.DefaultInfantryWeight, ["Bedford_MWD"] = 8*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["Blitz_36_6700A"] = 10*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["BMD_1"] = 9*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 4 passengers ["BMP_1"] = 8*POSITIONABLE.DefaultInfantryWeight, ["BMP_2"] = 7*POSITIONABLE.DefaultInfantryWeight, ["BMP_3"] = 8*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 7+2 passengers ["Boman"] = 25*POSITIONABLE.DefaultInfantryWeight, ["BTR_80"] = 9*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 7 passengers ["BTR_82A"] = 9*POSITIONABLE.DefaultInfantryWeight, -- new by kappa -- IRL should be 7 passengers ["BTR_D"] = 12*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 10 passengers ["Cobra"] = 8*POSITIONABLE.DefaultInfantryWeight, ["Land_Rover_101_FC"] = 11*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["Land_Rover_109_S3"] = 7*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["LAV_25"] = 6*POSITIONABLE.DefaultInfantryWeight, ["M_2_Bradley"] = 6*POSITIONABLE.DefaultInfantryWeight, ["M1043_HMMWV_Armament"] = 4*POSITIONABLE.DefaultInfantryWeight, ["M1045_HMMWV_TOW"] = 4*POSITIONABLE.DefaultInfantryWeight, ["M1126_Stryker_ICV"] = 9*POSITIONABLE.DefaultInfantryWeight, ["M1134_Stryker_ATGM"] = 9*POSITIONABLE.DefaultInfantryWeight, ["M2A1_halftrack"] = 9*POSITIONABLE.DefaultInfantryWeight, ["M_113"] = 9*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 11 passengers ["Marder"] = 6*POSITIONABLE.DefaultInfantryWeight, ["MCV_80"] = 9*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 7 passengers ["MLRS_FDDM"] = 4*POSITIONABLE.DefaultInfantryWeight, ["MTLB"] = 25*POSITIONABLE.DefaultInfantryWeight, -- IRL should be 11 passengers ["GAZ_66"] = 8*POSITIONABLE.DefaultInfantryWeight, ["GAZ_3307"] = 12*POSITIONABLE.DefaultInfantryWeight, ["GAZ_3308"] = 14*POSITIONABLE.DefaultInfantryWeight, ["Grad_FDDM"] = 6*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["KAMAZ_Truck"] = 12*POSITIONABLE.DefaultInfantryWeight, ["KrAZ6322"] = 12*POSITIONABLE.DefaultInfantryWeight, ["M_818"] = 12*POSITIONABLE.DefaultInfantryWeight, ["Tigr_233036"] = 6*POSITIONABLE.DefaultInfantryWeight, ["TPZ"] = 10*POSITIONABLE.DefaultInfantryWeight, -- Fuchs ["UAZ_469"] = 4*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["Ural_375"] = 12*POSITIONABLE.DefaultInfantryWeight, ["Ural_4320_31"] = 14*POSITIONABLE.DefaultInfantryWeight, ["Ural_4320_APA_5D"] = 10*POSITIONABLE.DefaultInfantryWeight, ["Ural_4320T"] = 14*POSITIONABLE.DefaultInfantryWeight, ["ZBD04A"] = 7*POSITIONABLE.DefaultInfantryWeight, -- new by kappa ["VAB_Mephisto"] = 8*POSITIONABLE.DefaultInfantryWeight, -- new by Apple ["tt_KORD"] = 6*POSITIONABLE.DefaultInfantryWeight, -- 2.7.1 HL/TT ["tt_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight, ["HL_KORD"] = 6*POSITIONABLE.DefaultInfantryWeight, ["HL_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight, ["CCKW_353"] = 16*POSITIONABLE.DefaultInfantryWeight, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers, } } --- Set Cargo Bay Weight Limit in kg. -- @param #POSITIONABLE self -- @param #number WeightLimit (Optional) Weight limit in kg. If not given, the value is taken from the descriptors or hard coded. function POSITIONABLE:SetCargoBayWeightLimit( WeightLimit ) if WeightLimit then --- -- User defined value --- self.__.CargoBayWeightLimit = WeightLimit elseif self.__.CargoBayWeightLimit ~= nil then -- Value already set ==> Do nothing! else --- -- Weightlimit is not provided, we will calculate it depending on the type of unit. --- -- Descriptors that contain the type name and for aircraft also weights. local Desc = self:GetDesc() self:F({Desc=Desc}) -- Unit type name. local TypeName=Desc.typeName or "Unknown Type" TypeName = string.gsub(TypeName,"[%p%s]","_") -- When an airplane or helicopter, we calculate the WeightLimit based on the descriptor. if self:IsAir() then -- Max takeoff weight if DCS descriptors have unrealstic values. local Weights = POSITIONABLE.CargoBayCapacityValues.Air -- Max (takeoff) weight (empty+fuel+cargo weight). local massMax= Desc.massMax or 0 -- Adjust value if set above. local maxTakeoff=Weights[TypeName] if maxTakeoff then massMax=maxTakeoff end -- Empty weight. local massEmpty=Desc.massEmpty or 0 -- Fuel. The descriptor provides the max fuel mass in kg. This needs to be multiplied by the relative fuel amount to calculate the actual fuel mass on board. local massFuelMax=Desc.fuelMassMax or 0 local relFuel=math.min(self:GetFuel() or 1.0, 1.0) -- We take 1.0 as max in case of external fuel tanks. local massFuel=massFuelMax*relFuel -- Number of soldiers according to DCS function --local troopcapacity=self:GetTroopCapacity() or 0 -- Calculate max cargo weight, which is the max (takeoff) weight minus the empty weight minus the actual fuel weight. local CargoWeight=massMax-(massEmpty+massFuel) -- Debug info. self:T(string.format("Setting Cargo bay weight limit [%s]=%d kg (Mass max=%d, empty=%d, fuelMax=%d kg (rel=%.3f), fuel=%d kg", TypeName, CargoWeight, massMax, massEmpty, massFuelMax, relFuel, massFuel)) --self:T(string.format("Descent Troop Capacity=%d ==> %d kg (for 95 kg soldier)", troopcapacity, troopcapacity*95)) -- Set value. self.__.CargoBayWeightLimit = CargoWeight elseif self:IsShip() then -- Hard coded cargo weights in kg. local Weights = POSITIONABLE.CargoBayCapacityValues.Naval self.__.CargoBayWeightLimit = ( Weights[TypeName] or 50000 ) else -- Hard coded number of soldiers. local Weights = POSITIONABLE.CargoBayCapacityValues.Ground -- Assuming that each passenger weighs 95 kg on average. local CargoBayWeightLimit = ( Weights[TypeName] or 0 ) self.__.CargoBayWeightLimit = CargoBayWeightLimit end end self:F({CargoBayWeightLimit = self.__.CargoBayWeightLimit}) end --- Get Cargo Bay Weight Limit in kg. -- @param #POSITIONABLE self -- @return #number Max cargo weight in kg. function POSITIONABLE:GetCargoBayWeightLimit() if self.__.CargoBayWeightLimit==nil then self:SetCargoBayWeightLimit() end return self.__.CargoBayWeightLimit end end --- Cargo --- Signal a flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self -- @param Utilities.Utils#FLARECOLOR FlareColor function POSITIONABLE:Flare( FlareColor ) self:F2() trigger.action.signalFlare( self:GetVec3(), FlareColor, 0 ) end --- Signal a white flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareWhite() self:F2() trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.White, 0 ) end --- Signal a yellow flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareYellow() self:F2() trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Yellow, 0 ) end --- Signal a green flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareGreen() self:F2() trigger.action.signalFlare( self:GetVec3(), trigger.flareColor.Green, 0 ) end --- Signal a red flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self function POSITIONABLE:FlareRed() self:F2() local Vec3 = self:GetVec3() if Vec3 then trigger.action.signalFlare( Vec3, trigger.flareColor.Red, 0 ) end end --- Smoke the POSITIONABLE. -- @param #POSITIONABLE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. -- @param #number Range The range in meters to randomize the smoking around the POSITIONABLE. -- @param #number AddHeight The height in meters to add to the altitude of the POSITIONABLE. function POSITIONABLE:Smoke( SmokeColor, Range, AddHeight ) self:F2() if Range then local Vec3 = self:GetRandomVec3( Range ) Vec3.y = Vec3.y + AddHeight or 0 trigger.action.smoke( Vec3, SmokeColor ) else local Vec3 = self:GetVec3() Vec3.y = Vec3.y + AddHeight or 0 trigger.action.smoke( self:GetVec3(), SmokeColor ) end end --- Smoke the POSITIONABLE Green. -- @param #POSITIONABLE self function POSITIONABLE:SmokeGreen() self:F2() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Green ) end --- Smoke the POSITIONABLE Red. -- @param #POSITIONABLE self function POSITIONABLE:SmokeRed() self:F2() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Red ) end --- Smoke the POSITIONABLE White. -- @param #POSITIONABLE self function POSITIONABLE:SmokeWhite() self:F2() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.White ) end --- Smoke the POSITIONABLE Orange. -- @param #POSITIONABLE self function POSITIONABLE:SmokeOrange() self:F2() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Orange ) end --- Smoke the POSITIONABLE Blue. -- @param #POSITIONABLE self function POSITIONABLE:SmokeBlue() self:F2() trigger.action.smoke( self:GetVec3(), trigger.smokeColor.Blue ) end --- Returns true if the unit is within a @{Core.Zone}. -- @param #POSITIONABLE self -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the unit is within the @{Core.Zone#ZONE_BASE} function POSITIONABLE:IsInZone( Zone ) self:F2( { self.PositionableName, Zone } ) if self:IsAlive() then local IsInZone = Zone:IsVec3InZone( self:GetVec3() ) return IsInZone end return false end --- Returns true if the unit is not within a @{Core.Zone}. -- @param #POSITIONABLE self -- @param Core.Zone#ZONE_BASE Zone The zone to test. -- @return #boolean Returns true if the unit is not within the @{Core.Zone#ZONE_BASE} function POSITIONABLE:IsNotInZone( Zone ) self:F2( { self.PositionableName, Zone } ) if self:IsAlive() then local IsNotInZone = not Zone:IsVec3InZone( self:GetVec3() ) return IsNotInZone else return false end end