diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 1ce9b2cae..470c34bbf 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -305,8 +305,8 @@ EVENTS = { -- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object. -- @field #string PlaceName The name of the airbase. -- --- @field #table weapon The weapon used during the event. --- @field #table Weapon +-- @field DCS#Weapon weapon The weapon used during the event. +-- @field DCS#Weapon Weapon The weapon used during the event. -- @field #string WeaponName Name of the weapon. -- @field DCS#Unit WeaponTgtDCSUnit Target DCS unit of the weapon. -- diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index fedbb3e19..f7de809a7 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -427,8 +427,8 @@ do -- Types --- Vec3 type is a 3D-vector. -- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain. -- @type Vec3 - -- @field #Distance x is directed to the north - -- @field #Distance z is directed to the east + -- @field #Distance x is directed to the North + -- @field #Distance z is directed to the East -- @field #Distance y is directed up --- Vec2 is a 2D-vector for the ground plane as a reference plane. diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index 8cba77152..52a3c86ba 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -2,16 +2,16 @@ -- -- ## Main Features: -- --- * Define operation phases --- * Define conditions when phases are over --- * Option to have branches in the phase tree --- * Dedicate resources to operations +-- * Convenient access to all DCS API functions +-- * Track weapon and get impact position +-- * Get launcher and target of weapon +-- * Destroy weapon before impact -- -- === -- -- ## Example Missions: -- --- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation). +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper%20-%20Weapon). -- -- === -- @@ -28,6 +28,10 @@ -- @field #number verbose Verbosity level. -- @field #string lid Class id string for output to DCS log file. -- @field DCS#Weapon weapon The DCS weapon object. +-- @field #function impactFunc Callback function for weapon impact. +-- @field #table impactArg Optional arguments for the impact callback function. +-- @field DCS#Vec3 vec3 Last known 3D position vector of the tracked weapon. +-- @field DCS#Position3 pos3 Last known 3D position and direction vector of the tracked weapon. -- @extends Wrapper.Positionable#POSITIONABLE --- *Before this time tomorrow I shall have gained a peerage, or Westminster Abbey.* -- Horatio Nelson @@ -35,11 +39,22 @@ -- === -- -- # The WEAPON Concept --- --- The wrapper class is different from most others as weapon objects cannot be found with a DCS API function like `getByName()`. --- They can only be found in DCS events like "Shot" -- +-- The WEAPON class offers an easy-to-use wrapper interface to all DCS API functions. +-- +-- Probably, the most striking highlight is that the position of the weapon can be tracked and its impact position can be determined, which is not +-- possible with the native DCS scripting engine functions. -- +-- **Note** that this wrapper class is different from most others as weapon objects cannot be found with a DCS API function like `getByName()`. +-- They can only be found in DCS events like the "Shot" event, where the weapon object is contained in the event data. +-- +-- # Dependencies +-- +-- This class is used (at least) in the MOOSE classes: +-- +-- * RANGE (to determine the impact points of bombs and missiles) +-- * ARTY (to destroy and replace shells with smoke or illumination) +-- * FOX (to destroy the missile before it hits the target) -- -- @field #WEAPON WEAPON = { @@ -47,8 +62,8 @@ WEAPON = { verbose = 0, } ---- Operation phase. --- @type WEAPON.Phase +--- Target data. +-- @type WEAPON.Target -- @field #number uid Unique ID of the phase. -- @field #string name Name of the phase. -- @field Core.Condition#CONDITION conditionOver Conditions when the phase is over. @@ -58,22 +73,6 @@ WEAPON = { -- @field #number duration Duration in seconds how long the phase should be active after it started. -- @field #WEAPON.Branch branch The branch this phase belongs to. ---- Operation branch. --- @type WEAPON.Branch --- @field #number uid Unique ID of the branch. --- @field #string name Name of the branch. --- @field #table phases Phases of this branch. --- @field #table edges Edges of this branch. - ---- Operation edge. --- @type WEAPON.Edge --- @field #number uid Unique ID of the edge. --- @field #WEAPON.Branch branchFrom The from branch. --- @field #WEAPON.Phase phaseFrom The from phase after which to switch. --- @field #WEAPON.Branch branchTo The branch to switch to. --- @field #WEAPON.Phase phaseTo The phase to switch to. --- @field Core.Condition#CONDITION conditionSwitch Conditions when to switch the branch. - --- Operation phase. -- @type WEAPON.PhaseStatus -- @field #string PLANNED Planned. @@ -94,17 +93,25 @@ WEAPON.version="0.0.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: A lot... +-- TODO: Destroy before impact. +-- TODO: Monitor target. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new WEAPON object. +--- Create a new WEAPON object from the DCS weapon object. -- @param #WEAPON self -- @param DCS#Weapon WeaponObject The DCS weapon object. -- @return #WEAPON self function WEAPON:New(WeaponObject) + -- Nil check on object. + if WeaponObject==nil then + env.error("ERROR: Weapon object does NOT exist") + return nil + end + -- Inherit everything from FSM class. local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON @@ -122,17 +129,27 @@ function WEAPON:New(WeaponObject) self.country=WeaponObject:getCountry() + -- Get DCS unit of the launcher. + local launcher=WeaponObject:getLauncher() - local text=string.format("FF Weapon: Name=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d", self.name, self.typeName, self.category, self.coalition, self.country) + self.launcherName="Unknown Launcher" + if launcher then + self.launcherName=launcher:getName() + self.launcher=UNIT:Find(launcher) + end + + -- Set log ID. + self.lid=string.format("[%s] %s | ", self.typeName, self.name) + + -- Set default parameters + self:SetTimeStepTrack() + + -- Debug info. + local text=string.format("FF Weapon: Name=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", + self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName) env.info(text) self:I(self.desc) - - - -- Set log ID. - self.lid=string.format("%s | ", self.name) - - self:Track() return self end @@ -150,17 +167,149 @@ function WEAPON:SetVerbosity(VerbosityLevel) return self end +--- Set track position time step. +-- @param #WEAPON self +-- @param #number TimeStep Time step in seconds when the position is updated. Default 0.01 sec ==> 100 evaluations per second. +-- @return #WEAPON self +function WEAPON:SetTimeStepTrack(TimeStep) + self.dtTrackPos=TimeStep or 0.01 + return self +end + + +--- Get the unit that launched the weapon. +-- @param #WEAPON self +-- @return Wrapper.Unit#UNIT Laucher +function WEAPON:GetLauncher() + return self.launcherUnit +end + +--- Get the target, which the weapon is guiding to. +-- @param #WEAPON self +-- @return Wrapper.Unit#UNIT Laucher +function WEAPON:GetTarget() + + local target=nil + if self.weapon then + + -- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase. + local object=self.weapon:getTarget() + + DCStarget:getCategory() + + target=UNIT:Find(DCStarget) + + end + + + return target +end + + +--- Get velocity vector of weapon. +-- @param #WEAPON self +-- @return DCS#Vec3 Velocity vector with x, y and z components in meters/second. +function WEAPON:GetVelocityVec3() + local Vvec3=nil + if self.weapon then + Vvec3=self.weapon:getVelocity() + end + return Vvec3 +end + +--- Get speed of weapon. +-- @param #WEAPON self +-- @return #number Speed in meters per second. +function WEAPON:GetSpeed() + + local speed=nil + + if self.weapon then + + local v=self:GetVelocityVec3() + + speed=UTILS.VecNorm(v) + + end + + return speed +end + +--- Get the current 3D position vector. +-- @param #WEAPON self +-- @return DCS#Vec3 +function WEAPON:GetVec3() + + local vec3=nil + if self.weapon then + vec3=self.weapon:getPoint() + end + + return vec3 +end + +--- Get type name. +-- @param #WEAPON self +-- @return #string The type name. +function WEAPON:GetTypeName() + return self.typeName +end + +--- Get coalition. +-- @param #WEAPON self +-- @return #number Coalition ID. +function WEAPON:GetCoalition() + return self.coalition +end + +--- Get country. +-- @param #WEAPON self +-- @return #number Country ID. +function WEAPON:GetCoalition() + return self.country +end + +--- Get DCS object. +-- @param #WEAPON self +-- @return DCS#Weapon The weapon object. +function WEAPON:GetDCSObject() + -- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE + return self.weapon +end + +--- Get the impact position. Note that this might not exist if the weapon has not impacted yet! +-- @param #WEAPON self +-- @return DCS#Vec3 Impact position vector (if any). +function WEAPON:GetImpactVec3() + return self.impactVec3 +end + + +--- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS... +-- @param #WEAPON self +-- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`. +function WEAPON:InAir() + local inAir=nil + if self.weapon then + inAir=self.weapon:inAir() + end + return inAir +end + + --- Check if weapon object (still) exists. -- @param #WEAPON self --- @return #boolean If `true`, the weapon object still exists. +-- @return #boolean If `true`, the weapon object still exists and `false` otherwise. Returns `nil` if weapon object itself is `nil`. function WEAPON:IsExist() - local isExist=false + local isExist=nil if self.weapon then isExist=self.weapon:isExist() end return isExist end + + --- Destroy the weapon object. -- @param #WEAPON self -- @param #number Delay Delay before destroy in seconds. @@ -178,61 +327,36 @@ function WEAPON:Destroy(Delay) return self end ---- Get velocity vector of weapon. --- @param #WEAPON self --- @return DCS#Vec3 Velocity vector with x, y and z components in meters/second. -function WEAPON:GetVelocity() - self.weapon:getVelocity() - return self -end - ---- Get speed of weapon. --- @param #WEAPON self --- @return #number Speed in meters per second. -function WEAPON:GetSpeed() - - local speed=nil - - if self.weapon then - - local v=self:GetVelocity() - - speed=UTILS.VecNorm(v) - - end - - return speed -end - ---- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS... --- @param #WEAPON self --- @return #boolean If `true`, weapon is in the air. -function WEAPON:InAir() - local inAir=self.weapon:inAir() - return inAir -end - ---- Get the current 3D position vector. --- @param #WEAPON self --- @return DCS#Vec3 -function WEAPON:GetVec3() - - local vec3=nil - if self.weapon then - vec3=self.weapon:getPoint() - end - - return vec3 -end - ---- Start tracking the position of the weapon. +--- Start tracking the position of the weapon until it impacts. +-- The position of the weapon is monitored in small time steps. Once the position cannot be determined anymore, the monitoring is stopped and the last known position is +-- the (approximate) impact point. Of course, the smaller the time step, the better the position can be determined. However, this can hit the performance as many +-- calculations per second need to be carried out. -- @param #WEAPON self -function WEAPON:Track() +-- @param #function FuncImpact Function called when weapon has impacted. First argument is the impact coordinate Core.Point#COORDINATE. +-- @param ... Optional arguments passed to the impact function after the impact coordinate. +-- @return #WEAPON self +-- +-- @usage +-- -- Function called on impact. +-- local function impactfunc(Coordinate, Weapon) +-- Coordinate:MarkToAll("Impact Coordinate of weapon") +-- end +-- +-- myweapon:Track(impactfunc) +-- +function WEAPON:TrackPosition(FuncImpact, ...) - -- Weapon is not yet "alife" just yet. Start timer in one second. - self:T( self.lid .. string.format( "Tracking weapon") ) - timer.scheduleFunction(WEAPON._Track, self, timer.getTime() + 0.001 ) + -- Debug info. + self:T(self.lid..string.format("Tracking weapon")) + -- Callback function on impact. + self.impactFunc=FuncImpact + self.impactArg=arg or {} + + -- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds. + timer.scheduleFunction(WEAPON._TrackPosition, self, timer.getTime() + 0.001) + + return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -242,20 +366,19 @@ end --- Track weapon until impact. -- @param #WEAPON self -- @param DCS#Time time Time in seconds. -function WEAPON:_Track(time) +-- @return #number Time when called next or nil if not called again. +function WEAPON:_TrackPosition(time) -- Debug info. - self:I(string.format("Tracking at T=%.5f", time)) + --self:I(string.format("Tracking at T=%.5f", time)) -- When the pcall returns a failure the weapon has hit. - local status, vec3= pcall( + local status, pos3= pcall( function() - local point=self.weapon:getPoint() + local point=self.weapon:getPosition() return point end ) - - self.dtTrack=0.01 if status then @@ -263,24 +386,91 @@ function WEAPON:_Track(time) -- Weapon is still in exists -- ------------------------------- - -- Remember this position. - self.vec3 = vec3 + -- Update last known position. + self.pos3 = pos3 + + -- Update last known vec3. + self.vec3 = self.pos3.p + + if self.verbose>=5 then + + local vec2={x=self.vec3.x, y=self.vec3.z} + + local height=land.getHeight(vec2) - -- Check again in ~0.005 seconds ==> 200 checks per second. - return time+self.dtTrack + -- Current height above ground level. + local agl=self.vec3.y-height + + -- Estimated IP (if any) + local ip=self:_GetIP(100) + + local d=0 + if ip then + d=UTILS.VecDist3D(self.vec3, ip) + end + + self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d)) + + end + + -- Check again in ~0.01 seconds ==> 100 checks per second. + return time+(self.dtTrackPos or 0.01) else - self.impactVec3=self.vec3 + --------------------------- + -- Weapon does NOT exist -- + --------------------------- + + -- Get intercept point from position (p) and direction (x) in 20 meters. + local ip = land.getIP(self.pos3.p, self.pos3.x, 20) --DCS#Vec3 - local coord=COORDINATE:NewFromVec3(self.vec3) + if ip then + env.info("FF Got intercept point!") + + -- Coordinate of the impact point. + local coord=COORDINATE:NewFromVec3(ip) + + -- Mark coordinate. + coord:MarkToAll("Intercept point") + coord:SmokeBlue() + + -- Distance to last known pos. + local d=UTILS.VecDist3D(ip, self.vec3) + + env.info(string.format("FF d(ip, vec3)=%.3f meters", d)) + + end + + -- Set impact vec3. + self.impactVec3=ip or self.vec3 - coord:MarkToAll("Impact point") + -- Set impact coordinate. + self.impactCoord=COORDINATE:NewFromVec3(self.vec3) + + --self.impactCoord:MarkToAll("Impact point") + + -- Call callback function. + if self.impactFunc then + self.impactFunc(self.impactCoord, self, self.impactArg) + end return nil end end +--- Compute estimated intercept/impact point (IP) based on last known position and direction. +-- @param #WEAPON self +-- @param #number Distance Distance in meters. Default 20 m. +-- @return DCS#Vec3 Estimated intercept/impact point. Can also return `nil`, if no IP can be determined. +function WEAPON:_GetIP(Distance) + + -- Get intercept point from position (p) and direction (x) in 20 meters. + local ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3 + + return ip +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------