From 6d967358da28d066b81e4f24630033ed288d4c89 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 4 Feb 2023 23:28:45 +0100 Subject: [PATCH] WEAPON v0.1.0 **WEAPON** - Improments of class - ARTY works - FOX works - RANGE should work, not tested. --- Moose Development/Moose/Core/Point.lua | 9 +- .../Moose/Functional/Artillery.lua | 254 +++++++++-------- Moose Development/Moose/Functional/Fox.lua | 145 +++++----- Moose Development/Moose/Functional/Range.lua | 18 +- Moose Development/Moose/Utilities/Utils.lua | 14 + Moose Development/Moose/Wrapper/Unit.lua | 2 +- Moose Development/Moose/Wrapper/Weapon.lua | 256 +++++++++++++++--- 7 files changed, 462 insertions(+), 236 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a52f4a9da..de6378a22 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1133,12 +1133,15 @@ do -- COORDINATE --- Return the 3D distance in meters between the target COORDINATE and the COORDINATE. -- @param #COORDINATE self - -- @param #COORDINATE TargetCoordinate The target COORDINATE. + -- @param #COORDINATE TargetCoordinate The target COORDINATE. Can also be a DCS#Vec3. -- @return DCS#Distance Distance The distance in meters. function COORDINATE:Get3DDistance( TargetCoordinate ) - local TargetVec3 = TargetCoordinate:GetVec3() + --local TargetVec3 = TargetCoordinate:GetVec3() + local TargetVec3 = {x=TargetCoordinate.x, y=TargetCoordinate.y, z=TargetCoordinate.z} local SourceVec3 = self:GetVec3() - return ( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + --local dist=( ( TargetVec3.x - SourceVec3.x ) ^ 2 + ( TargetVec3.y - SourceVec3.y ) ^ 2 + ( TargetVec3.z - SourceVec3.z ) ^ 2 ) ^ 0.5 + local dist=UTILS.VecDist3D(TargetVec3, SourceVec3) + return dist end diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 990f0e8a3..52ec23e15 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -103,6 +103,7 @@ -- @field #number coalition The coalition of the arty group. -- @field #boolean respawnafterdeath Respawn arty group after all units are dead. -- @field #number respawndelay Respawn delay in seconds. +-- @field #number dtTrack Time interval in seconds for weapon tracking. -- @extends Core.Fsm#FSM_CONTROLLABLE --- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can @@ -693,7 +694,7 @@ ARTY.db={ --- Arty script version. -- @field #string version -ARTY.version="1.2.0" +ARTY.version="1.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -801,6 +802,9 @@ function ARTY:New(group, alias) else self.ismobile=false end + + -- Set track time interval. + self.dtTrack=0.2 -- Set speed to 0.7 of maximum. self.Speed=self.SpeedMax * 0.7 @@ -1497,6 +1501,15 @@ function ARTY:SetStatusInterval(interval) return self end +--- Set time interval for weapon tracking. +-- @param #ARTY self +-- @param #number interval Time interval in seconds. Default 0.2 seconds. +-- @return self +function ARTY:SetTrackInterval(interval) + self.dtTrack=interval or 0.2 + return self +end + --- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed. -- @param #ARTY self -- @param #number waittime Time in seconds. Default 300 seconds. @@ -2129,6 +2142,95 @@ end -- Event Handling ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Function called during tracking of weapon. +-- @param Wrapper.Weapon#WEAPON weapon Weapon object. +-- @param #ARTY self ARTY object. +-- @param #ARTY.Target target Target of the weapon. +function ARTY._FuncTrack(weapon, self, target) + + -- Coordinate and distance to target. + local _coord=weapon.coordinate + local _dist=_coord:Get2DDistance(target.coord) + local _destroyweapon=false + + -- Debug + self:T3(self.lid..string.format("ARTY %s weapon to target dist = %d m", self.groupname,_dist)) + + if target.weapontype==ARTY.WeaponType.IlluminationShells then + + -- Check if within distace. + if _dist0 local _trackillu = self.currentTarget.weapontype==ARTY.WeaponType.IlluminationShells and self.Nillu>0 local _tracksmoke = self.currentTarget.weapontype==ARTY.WeaponType.SmokeShells and self.Nsmoke>0 + + if _tracknuke or _trackillu or _tracksmoke then - self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname)) - - local _peter={} - _peter.weapon=EventData.weapon - _peter.target=UTILS.DeepCopy(self.currentTarget) - - timer.scheduleFunction(_TrackWeapon, _peter, timer.getTime() + 2.0) + -- Debug info. + self:T(self.lid..string.format("ARTY %s: Tracking of weapon starts in two seconds.", self.groupname)) + + -- Create a weapon object. + local weapon=WEAPON:New(EventData.weapon) + + -- Set time step for tracking. + weapon:SetTimeStepTrack(self.dtTrack) + + -- Copy target. We need a copy because it might already be overwritten with the next target during flight of weapon. + local target=UTILS.DeepCopy(self.currentTarget) + + -- Set callback functions. + weapon:SetFuncTrack(ARTY._FuncTrack, self, target) + weapon:SetFuncImpact(ARTY._FuncImpact, self, target) + + -- Start tracking in 2 sec (arty ammo should fly a bit). + weapon:StartTrack(2) end -- Get current ammo. @@ -2842,6 +2856,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) end for _,targetname in pairs(notpossible) do self:E(self.lid..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.groupname, targetname)) + env.info("FF 1000",showMessageBox) self:RemoveTarget(targetname) end @@ -3931,9 +3946,10 @@ function ARTY:GetAmmo(display) return nammo, nshells, nrockets, nmissiles end - for _,unit in pairs(units) do + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT - if unit and unit:IsAlive() then + if unit then -- Output. local text=string.format("ARTY group %s - unit %s:\n", self.groupname, unit:GetName()) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 13ce14a41..5d2e35865 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -26,6 +26,7 @@ --- FOX class. -- @type FOX -- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #string lid Class id string for output to DCS log file. -- @field #table menuadded Table of groups the menu was added for. @@ -124,6 +125,7 @@ -- @field #FOX FOX = { ClassName = "FOX", + verbose = 0, Debug = false, lid = nil, menuadded = {}, @@ -185,6 +187,8 @@ FOX = { -- @field #string targetName Name of the target unit or "unknown". -- @field #string targetOrig Name of the "original" target, i.e. the one right after launched. -- @field #FOX.PlayerData targetPlayer Player that was targeted or nil. +-- @field Core.Point#COORDINATE missileCoord Missile coordinate during tracking. +-- @field Wrapper.Weapon#WEAPON Weapon Weapon object. --- Main radio menu on group level. -- @field #table MenuF10 Root menu table on group level. @@ -196,7 +200,7 @@ FOX.MenuF10Root=nil --- FOX class version. -- @field #string version -FOX.version="0.7.0" +FOX.version="0.8.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -500,6 +504,7 @@ function FOX:SetDisableF10Menu() return self end + --- Enable F10 menu for all players. -- @param #FOX self -- @return #FOX self @@ -510,6 +515,15 @@ function FOX:SetEnableF10Menu() return self end +--- Set verbosity level. +-- @param #FOX self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #FOX self +function FOX:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + --- Set default player setting for missile destruction. -- @param #FOX self -- @param #boolean switch If true missiles are destroyed. If false/nil missiles are not destroyed. @@ -605,7 +619,9 @@ function FOX:onafterStatus(From, Event, To) local clock=UTILS.SecondsToClock(time) -- Status. - self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate)) + if self.verbose>=1 then + self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate)) + end -- Check missile status. self:_CheckMissileStatus() @@ -713,7 +729,9 @@ function FOX:_CheckMissileStatus() if #self.missiles==0 then text=text.." none" end - self:I(self.lid..text) + if self.verbose>=2 then + self:I(self.lid..text) + end -- Remove inactive missiles. for i=#self.missiles,1,-1 do @@ -743,7 +761,7 @@ function FOX:_IsProtected(targetunit) if targetgroup then local targetname=targetgroup:GetName() - for _,_group in pairs(self.protectedset:GetSetObjects()) do + for _,_group in pairs(self.protectedset:GetSet()) do local group=_group --Wrapper.Group#GROUP if group then @@ -763,21 +781,24 @@ function FOX:_IsProtected(targetunit) end ---- Missle launch event. --- @param #FOX self +--- Function called from weapon tracking. -- @param Wrapper.Weapon#WEAPON weapon Weapon object. +-- @param #FOX self FOX object. -- @param #FOX.MissileData missile Fired missile -function FOX:_FuncTrack(weapon, missile) +function FOX._FuncTrack(weapon, self, missile) -- Missile coordinate. - missileCoord=COORDINATE:NewFromVec3(_lastBombPos) + local missileCoord= missile.missileCoord:UpdateFromVec3(weapon.vec3) --COORDINATE:NewFromVec3(_lastBombPos) -- Missile velocity in m/s. - local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity()) + local missileVelocity=weapon:GetSpeed() --UTILS.VecNorm(_ordnance:getVelocity()) -- Update missile target if necessary. self:GetMissileTarget(missile) + -- Target unit of the missile. + local target=nil --Wrapper.Unit#UNIT + if missile.targetUnit then ----------------------------------- @@ -868,13 +889,13 @@ function FOX:_FuncTrack(weapon, missile) if unit:GetName()~=missile.shooterName then -- Player position. - local playerCoord=unit:GetCoordinate() - - -- Distance. - local dist=missileCoord:Get3DDistance(playerCoord) + local playerVec3=unit:GetVec3() + -- Distance. + local dist=missileCoord:Get3DDistance(playerVec3) + -- Distance from shooter to player. - local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) + local Dshooter2player=missile.shotCoord:Get3DDistance(playerVec3) -- Update mindist if necessary. Only include players in range of missile + 50% safety margin. if (mindist==nil or dist destroy missile if in safe zone. - if destroymissile and self:_CheckCoordSafe(targetCoord) then + if destroymissile and self:_CheckCoordSafe(targetVec3) then -- Destroy missile. self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance)) - _ordnance:destroy() + weapon:Destroy() -- Missile is not active any more. missile.active=false @@ -941,7 +961,6 @@ function FOX:_FuncTrack(weapon, missile) -- Debug smoke. if self.Debug then missileCoord:SmokeRed() - targetCoord:SmokeGreen() end -- Create event. @@ -962,9 +981,9 @@ function FOX:_FuncTrack(weapon, missile) -- Increase dead counter. missile.targetPlayer.dead=missile.targetPlayer.dead+1 end - - -- Terminate timer. - return nil + + -- We could disable the tracking here but then the impact function would not be called. + --weapon.tracking=false else @@ -987,30 +1006,30 @@ function FOX:_FuncTrack(weapon, missile) dt=self.dt00 --0.01 end - -- Check again in dt seconds. - return timer.getTime()+dt + -- Set time step. + weapon:SetTimeStepTrack(dt) end else - -- Destroy missile. + -- No current target. self:T(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName)) - return timer.getTime()+0.1 + weapon:SetTimeStepTrack(0.1) end end --- Callback function on impact or destroy otherwise. --- @param #FOX self -- @param Wrapper.Weapon#WEAPON weapon Weapon object. --- @param #FOX.MissileData missile Fired missile -function FOX:_FuncImpact(weapon, missile) +-- @param #FOX self FOX object. +-- @param #FOX.MissileData missile Fired missile. +function FOX._FuncImpact(weapon, self, missile) - if target then + if missile.targetPlayer then -- Get human player. - local player=self:_GetPlayerFromUnit(target) + local player=missile.targetPlayer -- Check for player and distance < 10 km. if player and player.unit:IsAlive() then -- and missileCoord and player.unit:GetCoordinate():Get3DDistance(missileCoord)<10*1000 then @@ -1028,7 +1047,7 @@ function FOX:_FuncImpact(weapon, missile) --Terminate the timer. self:T(FOX.lid..string.format("Terminating missile track timer.")) - return nil + weapon.tracking=false end @@ -1090,18 +1109,17 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) end end - local weapon=WEAPON:New(missile.weapon) + -- Set callback function for tracking. + missile.Weapon:SetFuncTrack(FOX._FuncTrack, self, missile) - weapon:SetFuncTrack(FuncTrack) + -- Set callback function for impact. + missile.Weapon:SetFuncImpact(FOX._FuncImpact, self, missile) - weapon:SetFuncImpact(FuncImpact) - - -- Weapon is not yet "alife" just yet. Start timer with a little delay. self:T(FOX.lid..string.format("Tracking of missile starts in 0.0001 seconds.")) --timer.scheduleFunction(trackMissile, missile.weapon, timer.getTime()+0.0001) - weapon:StartTrack(0.0001) + missile.Weapon:StartTrack(0.0001) end @@ -1232,30 +1250,29 @@ end -- @param Core.Event#EVENTDATA EventData function FOX:OnEventShot(EventData) self:T2({eventshot=EventData}) + + -- Nil checks. + if EventData.Weapon==nil or EventData.IniDCSUnit==nil or EventData.weapon==nil then + return + end - if EventData.Weapon==nil then - return - end - if EventData.IniDCSUnit==nil then - return - end + -- Create a weapon object. + local weapon=WEAPON:New(EventData.weapon) -- Weapon data. - local _weapon = EventData.WeaponName + local _weapon = weapon:GetTypeName() local _target = EventData.Weapon:getTarget() local _targetName = "unknown" local _targetUnit = nil --Wrapper.Unit#UNIT -- Weapon descriptor. - local desc=EventData.Weapon:getDesc() + local desc=weapon.desc self:T2({desc=desc}) - -- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB - local weaponcategory=desc.category - -- Missile category: 1=AAM, 2=SAM, 6=OTHER local missilecategory=desc.missileCategory + -- Missile range. local missilerange=nil if missilecategory then missilerange=desc.rangeMaxAltMax @@ -1265,8 +1282,8 @@ function FOX:OnEventShot(EventData) self:T2(FOX.lid.."EVENT SHOT: FOX") self:T2(FOX.lid..string.format("EVENT SHOT: Ini unit = %s", tostring(EventData.IniUnitName))) self:T2(FOX.lid..string.format("EVENT SHOT: Ini group = %s", tostring(EventData.IniGroupName))) - self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(_weapon))) - self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weaponcategory))) + self:T2(FOX.lid..string.format("EVENT SHOT: Weapon type = %s", tostring(weapon:GetTypeName()))) + self:T2(FOX.lid..string.format("EVENT SHOT: Weapon categ = %s", tostring(weapon:GetCategory()))) self:T2(FOX.lid..string.format("EVENT SHOT: Missil categ = %s", tostring(missilecategory))) self:T2(FOX.lid..string.format("EVENT SHOT: Missil range = %s", tostring(missilerange))) @@ -1278,7 +1295,7 @@ function FOX:OnEventShot(EventData) end -- Track missiles of type AAM=1, SAM=2 or OTHER=6 - local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6) + local _track = weapon:IsMissile() and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6) -- Only track missiles if _track then @@ -1287,6 +1304,7 @@ function FOX:OnEventShot(EventData) missile.active=true missile.weapon=EventData.weapon + missile.Weapon=weapon missile.missileType=_weapon missile.missileRange=missilerange missile.missileName=EventData.weapon:getName() @@ -1299,6 +1317,7 @@ function FOX:OnEventShot(EventData) missile.fuseDist=desc.fuseDist missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass missile.targetOrig=missile.targetName + missile.missileCoord=COORDINATE:New(0,0,0) -- Set missile target name, unit and player. self:GetMissileTarget(missile) @@ -1617,7 +1636,7 @@ end --- Check if a coordinate lies within a safe training zone. -- @param #FOX self --- @param Core.Point#COORDINATE coord Coordinate to check. +-- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec3. -- @return #boolean True if safe. function FOX:_CheckCoordSafe(coord) @@ -1629,7 +1648,9 @@ function FOX:_CheckCoordSafe(coord) -- Loop over all zones. for _,_zone in pairs(self.safezones) do local zone=_zone --Core.Zone#ZONE - local inzone=zone:IsCoordinateInZone(coord) + local Vec2={x=coord.x, y=coord.z} + local inzone=zone:IsVec2InZone(Vec2) + --local inzone=zone:IsCoordinateInZone(coord) if inzone then return true end @@ -1640,7 +1661,7 @@ end --- Check if a coordinate lies within a launch zone. -- @param #FOX self --- @param Core.Point#COORDINATE coord Coordinate to check. +-- @param Core.Point#COORDINATE coord Coordinate to check. Can also be a DCS#Vec2. -- @return #boolean True if in launch zone. function FOX:_CheckCoordLaunch(coord) @@ -1652,7 +1673,9 @@ function FOX:_CheckCoordLaunch(coord) -- Loop over all zones. for _,_zone in pairs(self.launchzones) do local zone=_zone --Core.Zone#ZONE - local inzone=zone:IsCoordinateInZone(coord) + local Vec2={x=coord.x, y=coord.z} + local inzone=zone:IsVec2InZone(Vec2) + --local inzone=zone:IsCoordinateInZone(coord) if inzone then return true end diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 34b79bd7d..878bef1df 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1833,13 +1833,13 @@ function RANGE:OnEventHit( EventData ) end --- Function called on impact of a tracked weapon. --- @param #RANGE self -- @param Wrapper.Weapon#WEAPON weapon The weapon object. +-- @param #RANGE self RANGE object. -- @param #RANGE.PlayerData playerData Player data table. -- @param #number attackHdg Attack heading. -- @param #number attackAlt Attack altitude. -- @param #number attackVel Attack velocity. -function RANGE:_OnImpact(weapon, playerData, attackHdg, attackAlt, attackVel) +function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackVel) -- Get closet target to last position. local _closetTarget = nil -- #RANGE.BombTarget @@ -1964,10 +1964,6 @@ function RANGE:_OnImpact(weapon, playerData, attackHdg, attackAlt, attackVel) self:T( self.lid .. "Weapon impacted outside range zone." ) end - -- Terminate the timer - self:T( self.lid .. string.format( "Range %s, player %s: Terminating bomb track timer.", self.rangename, _playername ) ) - return nil - end --- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). @@ -1993,7 +1989,7 @@ function RANGE:OnEventShot( EventData ) -- Get player unit and name. local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) - -- Set this to larger value than the threshold. + -- Distance Player-to-Range. Set this to larger value than the threshold. local dPR = self.BombtrackThreshold * 2 -- Distance player to range. @@ -2027,14 +2023,6 @@ function RANGE:OnEventShot( EventData ) end ---- Check spawn queue and spawn aircraft if necessary. --- @param #RANGE self --- @param #string PlayerName Name of player. --- @return #RANGE.BombResult -function RANGE:_GetBombResults(PlayerName) - -end - ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM Functions ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index d81856f3b..26b5555fe 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1197,6 +1197,20 @@ function UTILS.HdgDiff(h1, h2) return math.abs(delta) end +--- Returns the heading from one vec3 to another vec3. +-- @param DCS#Vec3 a From vec3. +-- @param DCS#Vec3 b To vec3. +-- @return #number Heading in degrees. +function UTILS.HdgTo(a, b) + local dz=b.z-a.z + local dx=b.x-a.x + local heading=math.deg(math.atan2(dz, dx)) + if heading < 0 then + heading = 360 + heading + end + return heading +end + --- Translate 3D vector in the 2D (x,z) plane. y-component (usually altitude) unchanged. -- @param DCS#Vec3 a Vector in 3D with x, y, z components. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 596f670e4..1b582a546 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -167,7 +167,7 @@ end --- Get the DCS unit object. -- @param #UNIT self --- @return DCS#Unit +-- @return DCS#Unit The DCS unit object. function UNIT:GetDCSObject() local DCSUnit = Unit.getByName( self.UnitName ) diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua index b34000a09..b8437d7ca 100644 --- a/Moose Development/Moose/Wrapper/Weapon.lua +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -3,10 +3,12 @@ -- ## Main Features: -- -- * Convenient access to DCS API functions --- * Track weapon and get impact position +-- * Track weapon and get impact position -- * Get launcher and target of weapon -- * Define callback function when weapon impacts --- * Destroy weapon before impact +-- * Define callback function when tracking weapon +-- * Mark impact points on F10 map +-- * Put coloured smoke on impact points -- -- === -- @@ -31,7 +33,8 @@ -- @field DCS#Weapon weapon The DCS weapon object. -- @field #string name Name of the weapon object. -- @field #string typeName Type name of the weapon. --- @field #number category Weapon category 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X). +-- @field #number category Weapon category 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB, 4=TORPEDO (Weapon.Category.X). +-- @field #number categoryMissile Missile category 0=AAM, 1=SAM, 2=BM, 3=ANTI_SHIP, 4=CRUISE, 5=OTHER (Weapon.MissileCategory.X). -- @field #number coalition Coalition ID. -- @field #number country Country ID. -- @field DCS#Desc desc Descriptor table. @@ -45,11 +48,18 @@ -- @field #table trackArg Optional arguments for the track 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. +-- @field Core.Point#COORDINATE coordinate Coordinate object of the weapon. Can be used in other classes. -- @field DCS#Vec3 impactVec3 Impact 3D vector. -- @field Core.Point#COORDINATE impactCoord Impact coordinate. -- @field #number trackScheduleID Tracking scheduler ID. Can be used to remove/destroy the scheduler function. -- @field #boolean tracking If `true`, scheduler will keep tracking. Otherwise, function will return nil and stop tracking. --- @field #boolean markImpact If `true`, the impact point is marked on the F10 map. Requires tracking to be started. +-- @field #boolean impactMark If `true`, the impact point is marked on the F10 map. Requires tracking to be started. +-- @field #boolean impactSmoke If `true`, the impact point is marked by smoke. Requires tracking to be started. +-- @field #number impactSmokeColor Colour of impact point smoke. +-- @field #boolean impactDestroy If `true`, destroy weapon before impact. Requires tracking to be started and sufficiently small time step. +-- @field #number impactDestroyDist Distance in meters to the estimated impact point. If smaller, then weapon is destroyed. +-- @field #number distIP Distance in meters for the intercept point estimation. +-- @field Wrapper.Unit#UNIT target Last known target. -- @extends Wrapper.Positionable#POSITIONABLE --- *In the long run, the sharpest weapon of all is a kind and gentle spirit.* -- Anne Frank @@ -68,15 +78,21 @@ -- -- # Tracking -- --- The status of the weapon can be tracked with the @{#WEAPON.StartTrack}() function. This function will try to determin the position of the weapon in (normally) relatively --- small time steps. The time step can be set via the @{#WEAPON.SetTimeStepTrack} function and is by default set to 0.01 secons. +-- The status of the weapon can be tracked with the @{#WEAPON.StartTrack} function. This function will try to determin the position of the weapon in (normally) relatively +-- small time steps. The time step can be set via the @{#WEAPON.SetTimeStepTrack} function and is by default set to 0.01 seconds. -- -- Once the position cannot be retrieved any more, the weapon has impacted (or was destroyed otherwise) and the last known position is safed as the impact point. -- The impact point can be accessed with the @{#WEAPON.GetImpactVec3} or @{#WEAPON.GetImpactCoordinate} functions. -- +-- ## Impact Point Marking +-- +-- You can mark the impact point on the F10 map with @{#WEAPON.SetMarkImpact}. +-- +-- You can also trigger coloured smoke at the impact point via @{#WEAPON.SetSmokeImpact}. +-- -- ## Callback functions -- --- It is possible to define functions that are called during the tracking of the weapon and upon impact. +-- It is possible to define functions that are called during the tracking of the weapon and upon impact, which help you to customize further actions. -- -- ### Callback on Impact -- @@ -86,6 +102,38 @@ -- -- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack} -- +-- # Target +-- +-- If the weapon has a specific target, you can get it with the @{#WEAPON.GetTarget} function. Note that the object, which is returned can vary. Normally, it is a UNIT +-- but it could also be a STATIC object. +-- +-- Also note that the weapon does not always have a target, it can loose a target and re-aquire it and the target might change to another unit. +-- +-- You can get the target name with the @{#WEAPON.GetTargetName} function. +-- +-- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function. +-- +-- # Category +-- +-- The category (bomb, rocket, missile, shell, torpedo) of the weapon can be retrieved with the @{#WEAPON.GetCategory} function. +-- +-- You can check if the weapon is a +-- +-- * bomb with @{#WEAPON.IsBomb} +-- * rocket with @{#WEAPON.IsRocket} +-- * missile with @{#WEAPON.IsMissile} +-- * shell with @{#WEAPON.IsShell} +-- * torpedo with @{#WEAPON.IsTorpedo} +-- +-- # Parameters +-- +-- You can get various parameters of the weapon, *e.g.* +-- +-- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate} +-- * speed: @{#WEAPON.GetSpeed} +-- * coalition: @{#WEAPON.GetCoalition} +-- * country: @{#WEAPON.GetCountry} +-- -- # Dependencies -- -- This class is used (at least) in the MOOSE classes: @@ -103,7 +151,7 @@ WEAPON = { --- WEAPON class version. -- @field #string version -WEAPON.version="0.0.1" +WEAPON.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -149,7 +197,7 @@ function WEAPON:New(WeaponObject) end -- Get type name. - self.typeName=WeaponObject:getTypeName() + self.typeName=WeaponObject:getTypeName() or "Unknown Type" -- Get name of object. Usually a number like "1234567". self.name=WeaponObject:getName() @@ -170,18 +218,23 @@ function WEAPON:New(WeaponObject) self.launcherUnit=UNIT:Find(self.launcher) end + -- Init the coordinate of the weapon from that of the launcher. + self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint()) + -- Set log ID. self.lid=string.format("[%s] %s | ", self.typeName, self.name) -- Set default parameters self:SetTimeStepTrack() + self:SetDistanceInterceptPoint() -- 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) + local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", + self.version, self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName) + self:T(self.lid..text) - self:I(self.desc) + -- Descriptors. + self:T2(self.desc) return self end @@ -208,6 +261,19 @@ function WEAPON:SetTimeStepTrack(TimeStep) return self end +--- Set distance of intercept point for estimated impact point. +-- If the weapon cannot be tracked any more, the intercept point from its last known position and direction is used to get +-- a better approximation of the impact point. Can be useful when using longer time steps in the tracking and still achieve +-- a good result on the impact point. +-- It uses the DCS function [getIP](https://wiki.hoggitworld.com/view/DCS_func_getIP). +-- @param #WEAPON self +-- @param #number Distance Distance in meters. Default is 50 m. Set to 0 to deactivate. +-- @return #WEAPON self +function WEAPON:SetDistanceInterceptPoint(Distance) + self.distIP=Distance or 50 + return self +end + --- Mark impact point on the F10 map. This requires that the tracking has been started. -- @param #WEAPON self -- @param #boolean Switch If `true` or nil, impact is marked. @@ -215,14 +281,33 @@ end function WEAPON:SetMarkImpact(Switch) if Switch==false then - self.markImpact=false + self.impactMark=false else - self.markImpact=true + self.impactMark=true end return self end + +--- Put smoke on impact point. This requires that the tracking has been started. +-- @param #WEAPON self +-- @param #boolean Switch If `true` or nil, impact is smoked. +-- @param #number SmokeColor Color of smoke. Default is `SMOKECOLOR.Red`. +-- @return #WEAPON self +function WEAPON:SetSmokeImpact(Switch, SmokeColor) + + if Switch==false then + self.impactSmoke=false + else + self.impactSmoke=true + end + + self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red + + return self +end + --- Set callback function when weapon is tracked and still alive. The first argument will be the WEAPON object. -- Note that this can be called many times per second. So be careful for performance reasons. -- @param #WEAPON self @@ -284,20 +369,19 @@ function WEAPON:GetTarget() -- Get object category. local category=object:getCategory() - -- Get object name. + --Target name local name=object:getName() - + -- Debug info. - self:I(self.lid..string.format("Got Target Object %s, category=%d", name, category)) - + self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category)) if category==Object.Category.UNIT then - target=UNIT:Find(object) + target=UNIT:FindByName(name) elseif category==Object.Category.STATIC then - target=STATIC:Find(object) + target=STATIC:FindByName(name, false) elseif category==Object.Category.SCENERY then self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) @@ -308,10 +392,57 @@ function WEAPON:GetTarget() end end - return target end +--- Get the distance to the current target the weapon is guiding to. +-- @param #WEAPON self +-- @param #function ConversionFunction (Optional) Conversion function from meters to desired unit, *e.g.* `UTILS.MpsToKmph`. +-- @return #number Distance from weapon to target in meters. +function WEAPON:GetTargetDistance(ConversionFunction) + + -- Get the target of the weapon. + local target=self:GetTarget() --Wrapper.Unit#UNIT + + local distance=nil + if target then + + -- Current position of target. + local tv3=target:GetVec3() + + -- Current position of weapon. + local wv3=self:GetVec3() + + if tv3 and wv3 then + distance=UTILS.VecDist3D(tv3, wv3) + + if ConversionFunction then + distance=ConversionFunction(distance) + end + + end + + end + + return distance +end + + +--- Get name the current target the weapon is guiding to. +-- @param #WEAPON self +-- @return #string Name of the target or "None" if no target. +function WEAPON:GetTargetName() + + -- Get the target of the weapon. + local target=self:GetTarget() --Wrapper.Unit#UNIT + + local name="None" + if target then + name=target:GetName() + end + + return name +end --- Get velocity vector of weapon. -- @param #WEAPON self @@ -326,8 +457,9 @@ end --- Get speed of weapon. -- @param #WEAPON self +-- @param #function ConversionFunction (Optional) Conversion function from m/s to desired unit, *e.g.* `UTILS.MpsToKmph`. -- @return #number Speed in meters per second. -function WEAPON:GetSpeed() +function WEAPON:GetSpeed(ConversionFunction) local speed=nil @@ -337,6 +469,10 @@ function WEAPON:GetSpeed() speed=UTILS.VecNorm(v) + if ConversionFunction then + speed=ConversionFunction(speed) + end + end return speed @@ -344,7 +480,7 @@ end --- Get the current 3D position vector. -- @param #WEAPON self --- @return DCS#Vec3 +-- @return DCS#Vec3 Current position vector in 3D. function WEAPON:GetVec3() local vec3=nil @@ -355,6 +491,24 @@ function WEAPON:GetVec3() return vec3 end + +--- Get the current 2D position vector. +-- @param #WEAPON self +-- @return DCS#Vec2 Current position vector in 2D. +function WEAPON:GetVec2() + + local vec3=self:GetVec3() + + if vec3 then + + local vec2={x=vec3.x, y=vec3.z} + + return vec2 + end + + return nil +end + --- Get type name. -- @param #WEAPON self -- @return #string The type name. @@ -469,6 +623,7 @@ function WEAPON:Destroy(Delay) else if self.weapon then self:T(self.lid.."Destroying Weapon NOW!") + self:StopTrack() self.weapon:destroy() end end @@ -481,10 +636,11 @@ end -- 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 --- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec. This is also the minimum. +-- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec. -- @return #WEAPON self function WEAPON:StartTrack(Delay) + -- Set delay before start. Delay=math.max(Delay or 0.001, 0.001) -- Debug info. @@ -530,7 +686,9 @@ end function WEAPON:_TrackWeapon(time) -- Debug info. - self:T3(self.lid..string.format("Tracking at T=%.5f", time)) + if self.verbose>=20 then + self:I(self.lid..string.format("Tracking at T=%.5f", time)) + end -- Protected call to get the weapon position. If the position cannot be determined any more, the weapon has impacted and status is nil. local status, pos3= pcall( @@ -550,33 +708,41 @@ function WEAPON:_TrackWeapon(time) self.pos3 = pos3 -- Update last known vec3. - self.vec3 = self.pos3.p + self.vec3 = UTILS.DeepCopy(self.pos3.p) + + -- Update coordinate. + self.coordinate:UpdateFromVec3(self.vec3) -- Keep on tracking by returning the next time below. self.tracking=true -- Callback function. if self.trackFunc then - self.trackFunc(self, unpack(self.trackArg or {})) + self.trackFunc(self, unpack(self.trackArg)) end + -- Verbose output. if self.verbose>=5 then + -- Get vec2 of current position. local vec2={x=self.vec3.x, y=self.vec3.z} + -- Land hight. local height=land.getHeight(vec2) -- Current height above ground level. local agl=self.vec3.y-height -- Estimated IP (if any) - local ip=self:_GetIP(100) + local ip=self:_GetIP(self.distIP) + -- Distance between positon and estimated impact. local d=0 if ip then d=UTILS.VecDist3D(self.vec3, ip) end + -- Output. self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d)) end @@ -588,10 +754,12 @@ function WEAPON:_TrackWeapon(time) --------------------------- -- Get intercept point from position (p) and direction (x) in 50 meters. - local ip = self:_GetIP(50) + local ip = self:_GetIP(self.distIP) - if ip then - env.info("FF Got intercept point!") + if self.verbose>=10 and ip then + + -- Output. + self:I(self.lid.."Got intercept point!") -- Coordinate of the impact point. local coord=COORDINATE:NewFromVec3(ip) @@ -603,7 +771,8 @@ function WEAPON:_TrackWeapon(time) -- Distance to last known pos. local d=UTILS.VecDist3D(ip, self.vec3) - env.info(string.format("FF d(ip, vec3)=%.3f meters", d)) + -- Output. + self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d)) end @@ -614,10 +783,15 @@ function WEAPON:_TrackWeapon(time) self.impactCoord=COORDINATE:NewFromVec3(self.vec3) -- Mark impact point on F10 map. - if self.markImpact then + if self.impactMark then self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName)) end + -- Smoke on impact point. + if self.impactSmoke then + self.impactCoord:Smoke(self.impactSmokeColor) + end + -- Call callback function. if self.impactFunc then self.impactFunc(self, unpack(self.impactArg or {})) @@ -642,12 +816,20 @@ 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. +-- @param #number Distance Distance in meters. Default 50 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 + Distance=Distance or 50 + + local ip=nil --DCS#Vec3 + + if Distance>0 and self.pos3 then + + -- Get intercept point from position (p) and direction (x) in 20 meters. + ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3 + + end return ip end