From dec785447660f24158ef654ce3cfc234ea25a4fd Mon Sep 17 00:00:00 2001 From: "Mr.Alien" <124381209+MrAlien753@users.noreply.github.com> Date: Sat, 4 Feb 2023 22:01:55 +0100 Subject: [PATCH 1/4] Fix issue with getting thread level after a kill (#1908) --- Moose Development/Moose/Functional/Scoring.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index ea6933c61..57d0c3fc6 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -873,6 +873,7 @@ end function SCORING:OnEventBirth( Event ) if Event.IniUnit then + Event.IniUnit.ThreatLevel, Event.IniUnit.ThreatType = Event.IniUnit:GetThreatLevel() if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() Event.IniUnit.BirthTime = timer.getTime() @@ -1006,7 +1007,18 @@ function SCORING:_EventOnHit( Event ) PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT + -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth + if PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() + -- if this fails for some reason, set a good default value + if PlayerHit.ThreatType == nil then + PlayerHit.ThreatLevel = 1 + PlayerHit.ThreatType = "Unknown" + end + else + PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel + PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType + end -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then From c58918e00257024c3a2e60c8bc0682ee0f462f68 Mon Sep 17 00:00:00 2001 From: "Mr.Alien" <124381209+MrAlien753@users.noreply.github.com> Date: Sat, 4 Feb 2023 22:36:16 +0100 Subject: [PATCH 2/4] Add missing part of code in previous commit (#1910) --- Moose Development/Moose/Functional/Scoring.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 57d0c3fc6..b91e16fa1 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1117,7 +1117,18 @@ function SCORING:_EventOnHit( Event ) PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT + -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth + if PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() + -- if this fails for some reason, set a good default value + if PlayerHit.ThreatType == nil then + PlayerHit.ThreatLevel = 1 + PlayerHit.ThreatType = "Unknown" + end + else + PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel + PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType + end -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then From d4d305d53bd86ba9efdeba8bd4771a7b63db3a26 Mon Sep 17 00:00:00 2001 From: "Mr.Alien" <124381209+MrAlien753@users.noreply.github.com> Date: Sat, 4 Feb 2023 23:02:36 +0100 Subject: [PATCH 3/4] Fix confusing hit and kill messages with incorrect math (#1913) --- .../Moose/Functional/Scoring.lua | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index b91e16fa1..92fe5f16a 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -1034,19 +1034,20 @@ function SCORING:_EventOnHit( Event ) if InitCoalition then -- A coalition object was hit. if InitCoalition == TargetCoalition then - Player.Penalty = Player.Penalty + 10 - PlayerHit.Penalty = PlayerHit.Penalty + 10 + local Penalty = 10 + Player.Penalty = Player.Penalty + Penalty + PlayerHit.Penalty = PlayerHit.Penalty + Penalty PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 if TargetPlayerName ~= nil then -- It is a player hitting another player ... MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, + "Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) else MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, + "Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) @@ -1139,14 +1140,15 @@ function SCORING:_EventOnHit( Event ) if InitCoalition then -- A coalition object was hit, probably a static. if InitCoalition == TargetCoalition then -- TODO: Penalty according scale - Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty - PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty + local Penalty = 10 + Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty + PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty MESSAGE :NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - "Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty, + "Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) @@ -1157,7 +1159,7 @@ function SCORING:_EventOnHit( Event ) PlayerHit.Score = PlayerHit.Score + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - "Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty, + "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) @@ -1264,13 +1266,13 @@ function SCORING:_EventOnDeadOrCrash( Event ) if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty, + "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) else MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty, + "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) @@ -1292,13 +1294,13 @@ function SCORING:_EventOnDeadOrCrash( Event ) TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1 if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty, + "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) else MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty, + "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) From 772921c6b8a9e3c74fb876e86f1cb09576095981 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 4 Feb 2023 23:31:55 +0100 Subject: [PATCH 4/4] WEAPON - Added new class WEAPON --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Wrapper/Weapon.lua | 839 +++++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 841 insertions(+) create mode 100644 Moose Development/Moose/Wrapper/Weapon.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index c0ed4dd90..2d8bee643 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -45,6 +45,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua new file mode 100644 index 000000000..b8437d7ca --- /dev/null +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -0,0 +1,839 @@ +--- **Wrapper** - Weapon functions. +-- +-- ## Main Features: +-- +-- * Convenient access to DCS API functions +-- * Track weapon and get impact position +-- * Get launcher and target of weapon +-- * Define callback function when weapon impacts +-- * Define callback function when tracking weapon +-- * Mark impact points on F10 map +-- * Put coloured smoke on impact points +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper%20-%20Weapon). +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- @module Wrapper.Weapon +-- @image Wrapper_Weapon.png + + +--- WEAPON class. +-- @type WEAPON +-- @field #string ClassName Name of the class. +-- @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 #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, 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. +-- @field DCS#Unit launcher Launcher DCS unit. +-- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit. +-- @field #string launcherName Name of launcher unit. +-- @field #number dtTrack Time step in seconds for tracking scheduler. +-- @field #function impactFunc Callback function for weapon impact. +-- @field #table impactArg Optional arguments for the impact callback function. +-- @field #function trackFunc Callback function when weapon is tracked and alive. +-- @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 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 +-- +-- === +-- +-- # The WEAPON Concept +-- +-- 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. +-- +-- # 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 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, which help you to customize further actions. +-- +-- ### Callback on Impact +-- +-- The function called on impact can be set with @{#WEAPON.SetFuncImpact} +-- +-- ### Callback when Tracking +-- +-- 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: +-- +-- * 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 = { + ClassName = "WEAPON", + verbose = 0, +} + + +--- WEAPON class version. +-- @field #string version +WEAPON.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot... +-- TODO: Destroy before impact. +-- TODO: Monitor target. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- 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 + + -- Set DCS weapon object. + self.weapon=WeaponObject + + -- Descriptors containing a lot of info. + self.desc=WeaponObject:getDesc() + + -- This gives the object category which is always Object.Category.WEAPON! + --self.category=WeaponObject:getCategory() + + -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) + self.category = self.desc.category + + if self:IsMissile() and self.desc.missileCategory then + self.categoryMissile=self.desc.missileCategory + end + + -- Get type name. + self.typeName=WeaponObject:getTypeName() or "Unknown Type" + + -- Get name of object. Usually a number like "1234567". + self.name=WeaponObject:getName() + + -- Get coaliton of weapon. + self.coalition=WeaponObject:getCoalition() + + -- Get country of weapon. + self.country=WeaponObject:getCountry() + + -- Get DCS unit of the launcher. + self.launcher=WeaponObject:getLauncher() + + -- Get launcher of weapon. + self.launcherName="Unknown Launcher" + if self.launcher then + self.launcherName=self.launcher:getName() + 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("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) + + -- Descriptors. + self:T2(self.desc) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User API Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set verbosity level. +-- @param #WEAPON self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #WEAPON self +function WEAPON:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + 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.dtTrack=TimeStep or 0.01 + 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. +-- @return #WEAPON self +function WEAPON:SetMarkImpact(Switch) + + if Switch==false then + self.impactMark=false + else + 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 +-- @param #function FuncTrack Function called during tracking. +-- @param ... Optional function arguments. +-- @return #WEAPON self +function WEAPON:SetFuncTrack(FuncTrack, ...) + self.trackFunc=FuncTrack + self.trackArg=arg or {} + return self +end + +--- Set callback function when weapon impacted or was destroyed otherwise, *i.e.* cannot be tracked any more. +-- @param #WEAPON self +-- @param #function FuncImpact Function called once the weapon impacted. +-- @param ... Optional function arguments. +-- @return #WEAPON self +-- +-- @usage +-- -- Function called on impact. +-- local function OnImpact(Weapon) +-- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon") +-- end +-- +-- -- Set which function to call. +-- myweapon:SetFuncImpact(OnImpact) +-- +-- -- Start tracking. +-- myweapon:Track() +-- +function WEAPON:SetFuncImpact(FuncImpact, ...) + self.impactFunc=FuncImpact + self.impactArg=arg or {} + return self +end + + +--- Get the unit that launched the weapon. +-- @param #WEAPON self +-- @return Wrapper.Unit#UNIT Laucher unit. +function WEAPON:GetLauncher() + return self.launcherUnit +end + +--- Get the target, which the weapon is guiding to. +-- @param #WEAPON self +-- @return Wrapper.Object#OBJECT The target object, which can be a UNIT or STATIC object. +function WEAPON:GetTarget() + + local target=nil --Wrapper.Object#OBJECT + + if self.weapon then + + -- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase. + local object=self.weapon:getTarget() + + if object then + + -- Get object category. + local category=object:getCategory() + + --Target name + local name=object:getName() + + -- Debug info. + self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category)) + + if category==Object.Category.UNIT then + + target=UNIT:FindByName(name) + + elseif category==Object.Category.STATIC then + + target=STATIC:FindByName(name, false) + + elseif category==Object.Category.SCENERY then + self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!")) + else + self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category)) + end + + 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 +-- @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 +-- @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(ConversionFunction) + + local speed=nil + + if self.weapon then + + local v=self:GetVelocityVec3() + + speed=UTILS.VecNorm(v) + + if ConversionFunction then + speed=ConversionFunction(speed) + end + + end + + return speed +end + +--- Get the current 3D position vector. +-- @param #WEAPON self +-- @return DCS#Vec3 Current position vector in 3D. +function WEAPON:GetVec3() + + local vec3=nil + if self.weapon then + vec3=self.weapon:getPoint() + end + + 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. +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:GetCountry() + 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 vector. 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 + +--- Get the impact coordinate. Note that this might not exist if the weapon has not impacted yet! +-- @param #WEAPON self +-- @return Core.Point#COORDINATE Impact coordinate (if any). +function WEAPON:GetImpactCoordinate() + return self.impactCoord +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 and `false` otherwise. Returns `nil` if weapon object itself is `nil`. +function WEAPON:IsExist() + local isExist=nil + if self.weapon then + isExist=self.weapon:isExist() + end + return isExist +end + + +--- Check if weapon is a bomb. +-- @param #WEAPON self +-- @return #boolean If `true`, is a bomb. +function WEAPON:IsBomb() + return self.category==Weapon.Category.BOMB +end + +--- Check if weapon is a missile. +-- @param #WEAPON self +-- @return #boolean If `true`, is a missile. +function WEAPON:IsMissile() + return self.category==Weapon.Category.MISSILE +end + +--- Check if weapon is a rocket. +-- @param #WEAPON self +-- @return #boolean If `true`, is a missile. +function WEAPON:IsRocket() + return self.category==Weapon.Category.ROCKET +end + +--- Check if weapon is a shell. +-- @param #WEAPON self +-- @return #boolean If `true`, is a shell. +function WEAPON:IsShell() + return self.category==Weapon.Category.SHELL +end + +--- Check if weapon is a torpedo. +-- @param #WEAPON self +-- @return #boolean If `true`, is a torpedo. +function WEAPON:IsTorpedo() + return self.category==Weapon.Category.TORPEDO +end + + +--- Destroy the weapon object. +-- @param #WEAPON self +-- @param #number Delay Delay before destroy in seconds. +-- @return #WEAPON self +function WEAPON:Destroy(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0) + else + if self.weapon then + self:T(self.lid.."Destroying Weapon NOW!") + self:StopTrack() + self.weapon:destroy() + end + end + + return self +end + +--- Start tracking the weapon until it impacts or is destroyed otherwise. +-- 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 +-- @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. + self:T(self.lid..string.format("Start tracking weapon in %.4f sec", Delay)) + + -- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds. + self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay) + + return self +end + + +--- Stop tracking the weapon by removing the scheduler function. +-- @param #WEAPON self +-- @param #number Delay (Optional) Delay in seconds before the tracking is stopped. +-- @return #WEAPON self +function WEAPON:StopTrack(Delay) + + if Delay and Delay>0 then + -- Delayed call. + self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0) + else + + if self.trackScheduleID then + + timer.removeFunction(self.trackScheduleID) + + end + + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Track weapon until impact. +-- @param #WEAPON self +-- @param DCS#Time time Time in seconds. +-- @return #number Time when called next or nil if not called again. +function WEAPON:_TrackWeapon(time) + + -- Debug info. + 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( + function() + local point=self.weapon:getPosition() + return point + end + ) + + if status then + + ------------------------------- + -- Weapon is still in exists -- + ------------------------------- + + -- Update last known position. + self.pos3 = pos3 + + -- Update last known vec3. + 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)) + 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(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 + + else + + --------------------------- + -- Weapon does NOT exist -- + --------------------------- + + -- Get intercept point from position (p) and direction (x) in 50 meters. + local ip = self:_GetIP(self.distIP) + + 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) + + -- Mark coordinate. + coord:MarkToAll("Intercept point") + coord:SmokeBlue() + + -- Distance to last known pos. + local d=UTILS.VecDist3D(ip, self.vec3) + + -- Output. + self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d)) + + end + + -- Safe impact vec3. + self.impactVec3=ip or self.vec3 + + -- Safe impact coordinate. + self.impactCoord=COORDINATE:NewFromVec3(self.vec3) + + -- Mark impact point on F10 map. + 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 {})) + end + + -- Stop tracking by returning nil below. + self.tracking=false + + end + + -- Return next time the function is called or nil to stop the scheduler. + if self.tracking then + if self.dtTrack and self.dtTrack>0.001 then + return time+self.dtTrack + else + return nil + end + end + + return nil +end + +--- Compute estimated intercept/impact point (IP) based on last known position and direction. +-- @param #WEAPON self +-- @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) + + 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 + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 9531ed658..4f079b028 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -46,6 +46,7 @@ Wrapper/Static.lua Wrapper/Airbase.lua Wrapper/Scenery.lua Wrapper/Marker.lua +Wrapper/Weapon.lua Cargo/Cargo.lua Cargo/CargoUnit.lua