diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 87bcf9e4a..c8cb5dd05 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -880,8 +880,8 @@ do -- SET_GROUP -- -- * @{#SET_GROUP.FilterCoalitions}: Builds the SET_GROUP with the groups belonging to the coalition(s). -- * @{#SET_GROUP.FilterCategories}: Builds the SET_GROUP with the groups belonging to the category(ies). - -- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the gruops belonging to the country(ies). - -- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups starting with the same prefix string(s). + -- * @{#SET_GROUP.FilterCountries}: Builds the SET_GROUP with the groups belonging to the country(ies). + -- * @{#SET_GROUP.FilterPrefixes}: Builds the SET_GROUP with the groups *containing* the given string in the group name. **Attention!** Bad naming convention, as this not really filtering *prefixes*. -- * @{#SET_GROUP.FilterActive}: Builds the SET_GROUP with the groups that are only active. Groups that are inactive (late activation) won't be included in the set! -- -- For the Category Filter, extra methods have been added: @@ -1241,10 +1241,10 @@ do -- SET_GROUP end - --- Builds a set of groups of defined GROUP prefixes. - -- All the groups starting with the given prefixes will be included within the set. + --- Builds a set of groups that contain the given string in their group name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all groups that **contain** the string. -- @param #SET_GROUP self - -- @param #string Prefixes The prefix of which the group name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the group name. Can also be passed as a `#table` of strings. -- @return #SET_GROUP self function SET_GROUP:FilterPrefixes( Prefixes ) if not self.Filter.GroupPrefixes then @@ -1843,7 +1843,7 @@ do -- SET_UNIT -- * @{#SET_UNIT.FilterCategories}: Builds the SET_UNIT with the units belonging to the category(ies). -- * @{#SET_UNIT.FilterTypes}: Builds the SET_UNIT with the units belonging to the unit type(s). -- * @{#SET_UNIT.FilterCountries}: Builds the SET_UNIT with the units belonging to the country(ies). - -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units starting with the same prefix string(s). + -- * @{#SET_UNIT.FilterPrefixes}: Builds the SET_UNIT with the units sharing the same string(s) in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_UNIT.FilterActive}: Builds the SET_UNIT with the units that are only active. Units that are inactive (late activation) won't be included in the set! -- -- Once the filter criteria have been set for the SET_UNIT, you can start filtering using: @@ -2105,10 +2105,10 @@ do -- SET_UNIT end - --- Builds a set of units of defined unit prefixes. - -- All the units starting with the given prefixes will be included within the set. + --- Builds a set of UNITs that contain a given string in their unit name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all units that **contain** the string. -- @param #SET_UNIT self - -- @param #string Prefixes The prefix of which the unit name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit name. Can also be passed as a `#table` of strings. -- @return #SET_UNIT self function SET_UNIT:FilterPrefixes( Prefixes ) if not self.Filter.UnitPrefixes then @@ -2963,7 +2963,7 @@ do -- SET_STATIC -- * @{#SET_STATIC.FilterCategories}: Builds the SET_STATIC with the units belonging to the category(ies). -- * @{#SET_STATIC.FilterTypes}: Builds the SET_STATIC with the units belonging to the unit type(s). -- * @{#SET_STATIC.FilterCountries}: Builds the SET_STATIC with the units belonging to the country(ies). - -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units starting with the same prefix string(s). + -- * @{#SET_STATIC.FilterPrefixes}: Builds the SET_STATIC with the units containing the same string(s) in their name. **ATTENTION** bad naming convention as this *does not** only filter *prefixes*. -- -- Once the filter criteria have been set for the SET_STATIC, you can start filtering using: -- @@ -3176,10 +3176,10 @@ do -- SET_STATIC end - --- Builds a set of units of defined unit prefixes. - -- All the units starting with the given prefixes will be included within the set. + --- Builds a set of STATICs that contain the given string in their name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all statics that **contain** the string. -- @param #SET_STATIC self - -- @param #string Prefixes The prefix of which the unit name starts with. + -- @param #string Prefixes The string pattern(s) that need to be contained in the static name. Can also be passed as a `#table` of strings. -- @return #SET_STATIC self function SET_STATIC:FilterPrefixes( Prefixes ) if not self.Filter.StaticPrefixes then @@ -3675,7 +3675,7 @@ do -- SET_CLIENT -- * @{#SET_CLIENT.FilterCategories}: Builds the SET_CLIENT with the clients belonging to the category(ies). -- * @{#SET_CLIENT.FilterTypes}: Builds the SET_CLIENT with the clients belonging to the client type(s). -- * @{#SET_CLIENT.FilterCountries}: Builds the SET_CLIENT with the clients belonging to the country(ies). - -- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients starting with the same prefix string(s). + -- * @{#SET_CLIENT.FilterPrefixes}: Builds the SET_CLIENT with the clients containing the same string(s) in their unit/pilot name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_CLIENT.FilterActive}: Builds the SET_CLIENT with the units that are only active. Units that are inactive (late activation) won't be included in the set! -- -- Once the filter criteria have been set for the SET_CLIENT, you can start filtering using: @@ -3858,10 +3858,10 @@ do -- SET_CLIENT end - --- Builds a set of clients of defined client prefixes. - -- All the clients starting with the given prefixes will be included within the set. + --- Builds a set of CLIENTs that contain the given string in their unit/pilot name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all clients that **contain** the string. -- @param #SET_CLIENT self - -- @param #string Prefixes The prefix of which the client name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings. -- @return #SET_CLIENT self function SET_CLIENT:FilterPrefixes( Prefixes ) if not self.Filter.ClientPrefixes then @@ -4114,7 +4114,7 @@ do -- SET_PLAYER -- * @{#SET_PLAYER.FilterCategories}: Builds the SET_PLAYER with the clients belonging to the category(ies). -- * @{#SET_PLAYER.FilterTypes}: Builds the SET_PLAYER with the clients belonging to the client type(s). -- * @{#SET_PLAYER.FilterCountries}: Builds the SET_PLAYER with the clients belonging to the country(ies). - -- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients starting with the same prefix string(s). + -- * @{#SET_PLAYER.FilterPrefixes}: Builds the SET_PLAYER with the clients sharing the same string(s) in their unit/pilot name. **ATTENTION** Bad naming convention as this *does not* only filter prefixes. -- -- Once the filter criteria have been set for the SET_PLAYER, you can start filtering using: -- @@ -4293,10 +4293,10 @@ do -- SET_PLAYER end - --- Builds a set of clients of defined client prefixes. - -- All the clients starting with the given prefixes will be included within the set. + --- Builds a set of PLAYERs that contain the given string in their unit/pilot name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all player clients that **contain** the string. -- @param #SET_PLAYER self - -- @param #string Prefixes The prefix of which the client name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the unit/pilot name. Can also be passed as a `#table` of strings. -- @return #SET_PLAYER self function SET_PLAYER:FilterPrefixes( Prefixes ) if not self.Filter.ClientPrefixes then @@ -4874,7 +4874,7 @@ do -- SET_CARGO -- Filter criteria are defined by: -- -- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s). - -- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the prefix string(s). + -- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the same string(s). **ATTENTION** Bad naming convention as this *does not* only filter *prefixes*. -- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s). -- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies). -- @@ -5036,10 +5036,10 @@ do -- SET_CARGO end - --- (R2.1) Builds a set of cargos of defined cargo prefixes. - -- All the cargos starting with the given prefixes will be included within the set. + --- Builds a set of CARGOs that contain a given string in their name. + -- **Attention!** Bad naming convention as this **does not** filter only **prefixes** but all cargos that **contain** the string. -- @param #SET_CARGO self - -- @param #string Prefixes The prefix of which the cargo name starts with. + -- @param #string Prefixes The string pattern(s) that need to be in the cargo name. Can also be passed as a `#table` of strings. -- @return #SET_CARGO self function SET_CARGO:FilterPrefixes( Prefixes ) --R2.1 if not self.Filter.CargoPrefixes then @@ -5315,7 +5315,7 @@ do -- SET_ZONE -- You can set filter criteria to build the collection of zones in SET_ZONE. -- Filter criteria are defined by: -- - -- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern of prefix. + -- * @{#SET_ZONE.FilterPrefixes}: Builds the SET_ZONE with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- -- Once the filter criteria have been set for the SET_ZONE, you can start filtering using: -- @@ -5450,10 +5450,10 @@ do -- SET_ZONE - --- Builds a set of zones of defined zone prefixes. - -- All the zones starting with the given prefixes will be included within the set. + --- Builds a set of ZONEs that contain the given string in their name. + -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE self - -- @param #string Prefixes The prefix of which the zone name starts with. + -- @param #string Prefixes The string pattern(s) that need to be contained in the zone name. Can also be passed as a `#table` of strings. -- @return #SET_ZONE self function SET_ZONE:FilterPrefixes( Prefixes ) if not self.Filter.Prefixes then @@ -5652,7 +5652,7 @@ do -- SET_ZONE_GOAL -- You can set filter criteria to build the collection of zones in SET_ZONE_GOAL. -- Filter criteria are defined by: -- - -- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern of prefix. + -- * @{#SET_ZONE_GOAL.FilterPrefixes}: Builds the SET_ZONE_GOAL with the zones having a certain text pattern in their name. **ATTENTION!** Bad naming convention as this *does not* only filter *prefixes*. -- -- Once the filter criteria have been set for the SET_ZONE_GOAL, you can start filtering using: -- @@ -5768,10 +5768,10 @@ do -- SET_ZONE_GOAL - --- Builds a set of zones of defined zone prefixes. - -- All the zones starting with the given prefixes will be included within the set. + --- Builds a set of ZONE_GOALs that contain the given string in their name. + -- **ATTENTION!** Bad naming convention as this **does not** filter only **prefixes** but all zones that **contain** the string. -- @param #SET_ZONE_GOAL self - -- @param #string Prefixes The prefix of which the zone name starts with. + -- @param #string Prefixes The string pattern(s) that needs to be contained in the zone name. Can also be passed as a `#table` of strings. -- @return #SET_ZONE_GOAL self function SET_ZONE_GOAL:FilterPrefixes( Prefixes ) if not self.Filter.Prefixes then diff --git a/Moose Development/Moose/Functional/Designate.lua b/Moose Development/Moose/Functional/Designate.lua index a11aa21b8..72aaf9959 100644 --- a/Moose Development/Moose/Functional/Designate.lua +++ b/Moose Development/Moose/Functional/Designate.lua @@ -1001,7 +1001,13 @@ do -- DESIGNATE local ID = self.Detection:GetDetectedItemID( DetectedItem ) local MenuText = ID --.. ", " .. Coord:ToStringA2G( AttackGroup ) - MenuText = string.format( "(%3s) %s", Designating, MenuText ) + -- Use injected MenuName from TaskA2GDispatcher if using same Detection Object + if DetectedItem.DesignateMenuName then + MenuText = string.format( "(%3s) %s", Designating, DetectedItem.DesignateMenuName ) + else + MenuText = string.format( "(%3s) %s", Designating, MenuText ) + end + local DetectedMenu = MENU_GROUP_DELAYED:New( AttackGroup, MenuText, MenuDesignate ):SetTime( MenuTime ):SetTag( self.DesignateName ) -- Build the Lasing menu. diff --git a/Moose Development/Moose/Functional/Mantis.lua b/Moose Development/Moose/Functional/Mantis.lua index a06b5ccc2..bc043506b 100644 --- a/Moose Development/Moose/Functional/Mantis.lua +++ b/Moose Development/Moose/Functional/Mantis.lua @@ -23,21 +23,21 @@ -- Date: Jan 2021 ------------------------------------------------------------------------- ---- **MANTIS** class, extends @{#Core.Base#BASE} --- @type MANTIS #MANTIS +--- **MANTIS** class, extends #Core.Base#BASE +-- @type MANTIS -- @field #string Classname -- @field #string name Name of this Mantis --- @field #string SAM_Templates_Prefix Prefix to build the #GROUP_SET for SAM sites --- @field @{#Core.Set#GROUP_SET} SAM_Group The SAM #GROUP_SET --- @field #string EWR_Templates_Prefix Prefix to build the #GROUP_SET for EWR group --- @field @{#Core.Set#GROUP_SET} EWR_Group The EWR #GROUP_SET --- @field @{#Core.Set#GROUP_SET} Adv_EWR_Group The EWR #GROUP_SET used for advanced mode +-- @field #string SAM_Templates_Prefix Prefix to build the #SET_GROUP for SAM sites +-- @field Core.Set#SET_GROUP SAM_Group The SAM #SET_GROUP +-- @field #string EWR_Templates_Prefix Prefix to build the #SET_GROUP for EWR group +-- @field Core.Set#SET_GROUP EWR_Group The EWR #SET_GROUP +-- @field #Core.Set#SET_GROUP Adv_EWR_Group The EWR #SET_GROUP used for advanced mode -- @field #string HQ_Template_CC The ME name of the HQ object --- @field @{#Wrapper.Group#GROUP} HQ_CC The #GROUP object of the HQ +-- @field Wrapper.Group#GROUP HQ_CC The #GROUP object of the HQ -- @field #table SAM_Table Table of SAM sites -- @field #string lid Prefix for logging --- @field @{#Functional.Detection#DETECTION_AREAS} Detection The #DETECTION_AREAS object for EWR --- @field @{#Functional.Detection#DETECTION_AREAS} AWACS_Detection The #DETECTION_AREAS object for AWACS +-- @field Functional.Detection#DETECTION_AREAS Detection The #DETECTION_AREAS object for EWR +-- @field Functional.Detection#DETECTION_AREAS AWACS_Detection The #DETECTION_AREAS object for AWACS -- @field #boolean debug Switch on extra messages -- @field #boolean verbose Switch on extra logging -- @field #number checkradius Radius of the SAM sites @@ -51,7 +51,10 @@ -- @field #number adv_state Advanced mode state tracker -- @field #boolean advAwacs Boolean switch to use Awacs as a separate detection stream -- @field #number awacsrange Detection range of an optional Awacs unit --- @extends @{#Core.Base#BASE} +-- @field Functional.Shorad#SHORAD Shorad SHORAD Object, if available +-- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled +-- @field #number ShoradTime Timer in seconds, how long #SHORAD will be active after a detection inside of the defense range +-- @extends Core.Base#BASE --- *The worst thing that can happen to a good cause is, not to be skillfully attacked, but to be ineptly defended.* - Frédéric Bastiat @@ -163,7 +166,10 @@ MANTIS = { AWACS_Prefix = "", advAwacs = false, verbose = false, - awacsrange = 250000 + awacsrange = 250000, + Shorad = nil, + ShoradLink = false, + ShoradTime = 600, } ----------------------------------------------------------------------- @@ -230,6 +236,9 @@ do self.Adv_EWR_Group = nil self.AWACS_Prefix = awacs or nil self.awacsrange = 250000 --TODO: 250km, User Function to change + self.Shorad = nil + self.ShoradLink = false + self.ShoradTime = 600 if type(awacs) == "string" then self.advAwacs = true else @@ -373,7 +382,7 @@ do --- Function to set the HQ object for further use -- @param #MANTIS self - -- @param Wrapper.GROUP#GROUP The HQ #GROUP object to be set as HQ + -- @param Wrapper.GROUP#GROUP group The #GROUP object to be set as HQ function MANTIS:SetCommandCenter(group) local group = group or nil if group ~= nil then @@ -709,6 +718,27 @@ do return self end + --- Function to link up #MANTIS with a #SHORAD installation + -- @param #MANTIS self + -- @param Functional.Shorad#SHORAD Shorad The #SHORAD object + -- @param #number Shoradtime Number of seconds #SHORAD stays active post wake-up + function MANTIS:AddShorad(Shorad,Shoradtime) + local Shorad = Shorad or nil + local ShoradTime = Shoradtime or 600 + local ShoradLink = true + if Shorad:IsInstanceOf("SHORAD") then + self.ShoradLink = ShoradLink + self.Shorad = Shorad --#SHORAD + self.ShoradTime = Shoradtime -- #number + end + end + + --- Function to unlink #MANTIS from a #SHORAD installation + -- @param #MANTIS self + function MANTIS:RemoveShorad() + self.ShoradLink = false + end + ----------------------------------------------------------------------- -- MANTIS main functions ----------------------------------------------------------------------- @@ -743,8 +773,15 @@ do if samgroup:IsAlive() then -- switch off SAM samgroup:OptionAlarmStateRed() - --samgroup:OptionROEWeaponFree() - --samgroup:SetAIOn() + -- link in to SHORAD if available + -- TODO Test integration fully + if self.ShoradLink then + local Shorad = self.Shorad + local radius = self.checkradius + local ontime = self.ShoradTime + Shorad:WakeUpShorad(name, radius, ontime) + end + -- debug output local text = string.format("SAM %s switched to alarm state RED!", name) local m=MESSAGE:New(text,10,"MANTIS"):ToAllIf(self.debug) if self.verbose then env.info(self.lid..text) end diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 4e2f00126..ae886d712 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -17,7 +17,7 @@ -- -- ### Authors: **FlightControl**, **applevangelist** -- --- Last Update: Dec 2020 +-- Last Update: Feb 2021 -- -- === -- @@ -51,9 +51,44 @@ SEAD = { EngagementRange = 75 -- default 75% engagement range Feature Request #1355 } + -- TODO Complete list? + --- Missile enumerators + -- @field Harms + SEAD.Harms = { + --[[ + ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired + ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired + ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired + ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired + ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired + ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired + ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired + ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired + ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired + ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired + ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired + ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired + ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired + ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired + --]] + ["AGM_88"] = "AGM_88", + ["AGM_45"] = "AGM_45", + ["AGM_122"] = "AGM_122", + ["AGM_84"] = "AGM_84", + ["AGM_45"] = "AGM_45", + ["ALARN"] = "ALARM", + ["LD-10"] = "LD-10", + ["X_58"] = "X_58", + ["X_28"] = "X_28", + ["X_25"] = "X_25", + ["X_31"] = "X_31", + ["Kh25"] = "Kh25", + } + --- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles. -- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions... -- Chances are big that the missile will miss. +-- @param #SEAD self -- @param table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCS mission editor on which evasive actions need to be taken. -- @return SEAD -- @usage @@ -74,7 +109,7 @@ function SEAD:New( SEADGroupPrefixes ) end self:HandleEvent( EVENTS.Shot ) - self:I("*** SEAD - Started Version 0.2.2") + self:I("*** SEAD - Started Version 0.2.5") return self end @@ -112,7 +147,20 @@ function SEAD:SetEngagementRange(range) return self end ---- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. + --- Check if a known HARM was fired + -- @param #SEAD self + -- @param #string WeaponName + -- @return #boolean Returns true for a match + function SEAD:_CheckHarms(WeaponName) + self:F( { WeaponName } ) + local hit = false + for _,_name in pairs (SEAD.Harms) do + if string.find(WeaponName,_name,1) then hit = true end + end + return hit + end + +--- Detects if an SAM site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see SEAD -- @param #SEAD -- @param Core.Event#EVENTDATA EventData @@ -127,7 +175,7 @@ function SEAD:OnEventShot( EventData ) self:T( "*** SEAD - Missile Launched = " .. SEADWeaponName) self:T({ SEADWeapon }) - --check for SEAD missiles + --[[check for SEAD missiles if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired or SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired @@ -155,12 +203,13 @@ function SEAD:OnEventShot( EventData ) SEADWeaponName == "weapons.missiles.AGM_84A" --AGM84 anti-radiation missiles fired or SEADWeaponName == "weapons.missiles.AGM_84H" --AGM84 anti-radiation missiles fired - then + --]] + if self:_CheckHarms(SEADWeaponName) then local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMimname = Unit.getName(_targetMim) -- Unit name - local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted grouo + local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) --targeted group local _targetMimgroupName = _targetMimgroup:getName() -- group name local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill self:T( self.SEADGroupPrefixes ) diff --git a/Moose Development/Moose/Functional/Shorad.lua b/Moose Development/Moose/Functional/Shorad.lua new file mode 100644 index 000000000..7ed638f43 --- /dev/null +++ b/Moose Development/Moose/Functional/Shorad.lua @@ -0,0 +1,460 @@ +--- **Functional** -- Short Range Air Defense System +-- +-- === +-- +-- **SHORAD** - Short Range Air Defense System +-- Controls a network of short range air/missile defense groups. +-- +-- === +-- +-- ## Missions: +-- +-- ### [SHORAD - Short Range Air Defense](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/SRD%20-%20SHORAD%20Defense) +-- +-- === +-- +-- ### Author : **applevangelist ** +-- +-- @module Functional.Shorad +-- @image Functional.Shorad.jpg +-- +-- Date: Feb 2021 + +------------------------------------------------------------------------- +--- **SHORAD** class, extends Core.Base#BASE +-- @type SHORAD +-- @field #string ClassName +-- @field #string name Name of this Shorad +-- @field #boolean debug Set the debug state +-- @field #string Prefixes String to be used to build the @{#Core.Set#SET_GROUP} +-- @field #number Radius Shorad defense radius in meters +-- @field Core.Set#SET_GROUP Groupset The set of Shorad groups +-- @field Core.Set#SET_GROUP Samset The set of SAM groups to defend +-- @field #string Coalition The coalition of this Shorad +-- @field #number ActiveTimer How long a Shorad stays active after wake-up in seconds +-- @field #table ActiveGroups Table for the timer function +-- @field #string lid The log ID for the dcs.log +-- @field #boolean DefendHarms Default true, intercept incoming HARMS +-- @field #boolean DefendMavs Default true, intercept incoming AG-Missiles +-- @field #number DefenseLowProb Default 70, minimum detection limit +-- @field #number DefenseHighProb Default 90, maximim detection limit +-- @extends Core.Base#BASE + +--- *Good friends are worth defending.* Mr Tushman, Wonder (the Movie) +-- +-- Simple Class for a more intelligent Short Range Air Defense System +-- +-- #SHORAD +-- Moose derived missile intercepting short range defense system. +-- Protects a network of SAM sites. Uses events to switch on the defense groups closest to the enemy. +-- Easily integrated with @{Functional.Mantis#MANTIS} to complete the defensive system setup. +-- +-- ## Usage +-- +-- Set up a #SET_GROUP for the SAM sites to be protected: +-- +-- `local SamSet = SET_GROUP:New():FilterPrefixes("Red SAM"):FilterCoalitions("red"):FilterStart()` +-- +-- By default, SHORAD will defense against both HARMs and AG-Missiles with short to medium range. The default defense probability is 70-90%. +-- When a missile is detected, SHORAD will activate defense groups in the given radius around the target for 10 minutes. It will *not* react to friendly fire. +-- +-- ### Start a new SHORAD system, parameters are: +-- +-- * Name: Name of this SHORAD. +-- * ShoradPrefix: Filter for the Shorad #SET_GROUP. +-- * Samset: The #SET_GROUP of SAM sites to defend. +-- * Radius: Defense radius in meters. +-- * ActiveTimer: Determines how many seconds the systems stay on red alert after wake-up call. +-- * Coalition: Coalition, i.e. "blue", "red", or "neutral".* +-- +-- `myshorad = SHORAD:New("RedShorad", "Red SHORAD", SamSet, 25000, 600, "red")` +-- +-- ## Customize options +-- +-- * SHORAD:SwitchDebug(debug) +-- * SHORAD:SwitchHARMDefense(onoff) +-- * SHORAD:SwitchAGMDefense(onoff) +-- * SHORAD:SetDefenseLimits(low,high) +-- * SHORAD:SetActiveTimer(seconds) +-- * SHORAD:SetDefenseRadius(meters) +-- +-- @field #SHORAD +SHORAD = { + ClassName = "SHORAD", + name = "MyShorad", + debug = false, + Prefixes = "", + Radius = 20000, + Groupset = nil, + Samset = nil, + Coalition = nil, + ActiveTimer = 600, --stay on 10 mins + ActiveGroups = {}, + lid = "", + DefendHarms = true, + DefendMavs = true, + DefenseLowProb = 70, + DefenseHighProb = 90, +} + +----------------------------------------------------------------------- +-- SHORAD System +----------------------------------------------------------------------- + +do + -- TODO Complete list? + --- Missile enumerators + -- @field Harms + SHORAD.Harms = { + --[[ + ["X58"] = "weapons.missiles.X_58", --Kh-58X anti-radiation missiles fired + ["Kh25"] = "weapons.missiles.Kh25MP_PRGS1VP", --Kh-25MP anti-radiation missiles fired + ["X25"] = "weapons.missiles.X_25MP", --Kh-25MPU anti-radiation missiles fired + ["X28"] = "weapons.missiles.X_28", --Kh-28 anti-radiation missiles fired + ["X31"] = "weapons.missiles.X_31P", --Kh-31P anti-radiation missiles fired + ["AGM45A"] = "weapons.missiles.AGM_45A", --AGM-45A anti-radiation missiles fired + ["AGM45"] = "weapons.missiles.AGM_45", --AGM-45B anti-radiation missiles fired + ["AGM88"] = "weapons.missiles.AGM_88", --AGM-88C anti-radiation missiles fired + ["AGM122"] = "weapons.missiles.AGM_122", --AGM-122 Sidearm anti-radiation missiles fired + ["LD10"] = "weapons.missiles.LD-10", --LD-10 anti-radiation missiles fired + ["ALARM"] = "weapons.missiles.ALARM", --ALARM anti-radiation missiles fired + ["AGM84E"] = "weapons.missiles.AGM_84E", --AGM84 anti-radiation missiles fired + ["AGM84A"] = "weapons.missiles.AGM_84A", --AGM84 anti-radiation missiles fired + ["AGM84H"] = "weapons.missiles.AGM_84H", --AGM84 anti-radiation missiles fired + --]] + ["AGM_88"] = "AGM_88", + ["AGM_45"] = "AGM_45", + ["AGM_122"] = "AGM_122", + ["AGM_84"] = "AGM_84", + ["AGM_45"] = "AGM_45", + ["ALARN"] = "ALARM", + ["LD-10"] = "LD-10", + ["X_58"] = "X_58", + ["X_28"] = "X_28", + ["X_25"] = "X_25", + ["X_31"] = "X_31", + ["Kh25"] = "Kh25", + } + + --- TODO complete list? + -- @field Mavs + SHORAD.Mavs = { + ["AGM"] = "AGM", + ["C-701"] = "C-701", + ["Kh25"] = "Kh25", + ["Kh29"] = "Kh29", + ["Kh31"] = "Kh31", + ["Kh66"] = "Kh66", + } + + --- Instantiates a new SHORAD object + -- @param #SHORAD self + -- @param #string Name Name of this SHORAD + -- @param #string ShoradPrefix Filter for the Shorad #SET_GROUP + -- @param Core.Set#SET_GROUP Samset The #SET_GROUP of SAM sites to defend + -- @param #number Radius Defense radius in meters, used to switch on groups + -- @param #number ActiveTimer Determines how many seconds the systems stay on red alert after wake-up call + -- @param #string Coalition Coalition, i.e. "blue", "red", or "neutral" + function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition) + local self = BASE:Inherit( self, BASE:New() ) + self:F({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition}) + + local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart() + + self.name = Name or "MyShorad" + self.Prefixes = ShoradPrefix or "SAM SHORAD" + self.Radius = Radius or 20000 + self.Coalition = Coalition or "blue" + self.Samset = Samset or GroupSet + self.ActiveTimer = ActiveTimer or 600 + self.ActiveGroups = {} + self.Groupset = GroupSet + self:HandleEvent( EVENTS.Shot ) + self.DefendHarms = true + self.DefendMavs = true + self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin + self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin + self:I("*** SHORAD - Started Version 0.0.2") + -- Set the string id for output to DCS.log file. + self.lid=string.format("SHORAD %s | ", self.name) + self:_InitState() + return self + end + + --- Initially set all groups to alarm state GREEN + -- @param #SHORAD self + function SHORAD:_InitState() + local table = {} + local set = self.Groupset + self:T({set = set}) + local aliveset = set:GetAliveSet() --#table + for _,_group in pairs (aliveset) do + _group:OptionAlarmStateGreen() --Wrapper.Group#GROUP + end + -- gather entropy + for i=1,10 do + math.random() + end + end + + --- Switch debug state + -- @param #SHORAD self + -- @param #boolean debug Switch debug on (true) or off (false) + function SHORAD:SwitchDebug(debug) + self:F( { debug } ) + local onoff = debug or false + if debug then + self.debug = true + --tracing + BASE:TraceOn() + BASE:TraceClass("SHORAD") + else + self.debug = false + BASE:TraceOff() + end + end + + --- Switch defense for HARMs + -- @param #SHORAD self + -- @param #boolean onoff + function SHORAD:SwitchHARMDefense(onoff) + self:F( { onoff } ) + local onoff = onoff or true + self.DefendHarms = onoff + end + + --- Switch defense for AGMs + -- @param #SHORAD self + -- @param #boolean onoff + function SHORAD:SwitchAGMDefense(onoff) + self:F( { onoff } ) + local onoff = onoff or true + self.DefendMavs = onoff + end + + --- Set defense probability limits + -- @param #SHORAD self + -- @param #number low Minimum detection limit, integer 1-100 + -- @param #number high Maximum detection limit integer 1-100 + function SHORAD:SetDefenseLimits(low,high) + self:F( { low, high } ) + local low = low or 70 + local high = high or 90 + if (low < 0) or (low > 100) or (low > high) then + low = 70 + end + if (high < 0) or (high > 100) or (high < low ) then + high = 90 + end + self.DefenseLowProb = low + self.DefenseHighProb = high + end + + --- Set the number of seconds a SHORAD site will stay active + -- @param #SHORAD self + -- @param #number seconds Number of seconds systems stay active + function SHORAD:SetActiveTimer(seconds) + local timer = seconds or 600 + if timer < 0 then + timer = 600 + end + self.ActiveTimer = timer + end + + --- Set the number of meters for the SHORAD defense zone + -- @param #SHORAD self + -- @param #number meters Radius of the defense search zone in meters. #SHORADs in this range around a targeted group will go active + function SHORAD:SetDefenseRadius(meters) + local radius = meters or 20000 + if radius < 0 then + radius = 20000 + end + self.Radius = radius + end + + --- Check if a HARM was fired + -- @param #SHORAD self + -- @param #string WeaponName + -- @return #boolean Returns true for a match + function SHORAD:_CheckHarms(WeaponName) + self:F( { WeaponName } ) + local hit = false + if self.DefendHarms then + for _,_name in pairs (SHORAD.Harms) do + if string.find(WeaponName,_name,1) then hit = true end + end + end + return hit + end + + --- Check if an AGM was fired + -- @param #SHORAD self + -- @param #string WeaponName + -- @return #boolean Returns true for a match + function SHORAD:_CheckMavs(WeaponName) + self:F( { WeaponName } ) + local hit = false + if self.DefendMavs then + for _,_name in pairs (SHORAD.Mavs) do + if string.find(WeaponName,_name,1) then hit = true end + end + end + return hit + end + + --- Check the coalition of the attacker + -- @param #SHORAD self + -- @param #string Coalition name + -- @return #boolean Returns false for a match + function SHORAD:_CheckCoalition(Coalition) + local owncoalition = self.Coalition + local othercoalition = "" + if Coalition == 0 then + othercoalition = "neutral" + elseif Coalition == 1 then + othercoalition = "red" + else + othercoalition = "blue" + end + self:T({owncoalition = owncoalition, othercoalition = othercoalition}) + if owncoalition ~= othercoalition then + return true + else + return false + end + end + + --- Check if the missile is aimed at a SHORAD + -- @param #SHORAD self + -- @param #string TargetGroupName Name of the target group + -- @return #boolean Returns true for a match, else false + function SHORAD:_CheckShotAtShorad(TargetGroupName) + local tgtgrp = TargetGroupName + local shorad = self.Groupset + local shoradset = shorad:GetAliveSet() --#table + local returnname = false + for _,_groups in pairs (shoradset) do + local groupname = _groups:GetName() + if string.find(groupname, tgtgrp, 1) then + returnname = true + end + end + return returnname + end + + --- Check if the missile is aimed at a SAM site + -- @param #SHORAD self + -- @param #string TargetGroupName Name of the target group + -- @return #boolean Returns true for a match, else false + function SHORAD:_CheckShotAtSams(TargetGroupName) + local tgtgrp = TargetGroupName + local shorad = self.Samset + local shoradset = shorad:GetAliveSet() --#table + local returnname = false + for _,_groups in pairs (shoradset) do + local groupname = _groups:GetName() + if string.find(groupname, tgtgrp, 1) then + returnname = true + end + end + return returnname + end + + --- Calculate if the missile shot is detected + -- @param #SHORAD self + -- @return #boolean Returns true for a detection, else false + function SHORAD:_ShotIsDetected() + local IsDetected = false + local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value + local ActualDetection = math.random(1,100) -- value for this shot + if ActualDetection <= DetectionProb then + IsDetected = true + end + return IsDetected + end + + --- Wake up #SHORADs in a zone with diameter Radius for ActiveTimer seconds + -- @param #SHORAD self + -- @param #string TargetGroup Name of the target group used to build the #ZONE + -- @param #number Radius Radius of the #ZONE + -- @param #number ActiveTimer Number of seconds to stay active + -- @usage Use this function to integrate with other systems. + function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer) + self:F({TargetGroup, Radius, ActiveTimer}) + local targetgroup = GROUP:FindByName(TargetGroup) + local targetzone = ZONE_GROUP:New("Shorad",targetgroup,Radius) -- create a defense zone to check + local groupset = self.Groupset --Core.Set#SET_GROUP + local shoradset = groupset:GetAliveSet() --#table + -- local function to switch off shorad again + local function SleepShorad(group) + local groupname = group:GetName() + self.ActiveGroups[groupname] = nil + group:OptionAlarmStateGreen() + local text = string.format("Sleeping SHORAD %s", group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + end + -- go through set and find the one(s) to activate + for _,_group in pairs (shoradset) do + if _group:IsAnyInZone(targetzone) then + local text = string.format("Waking up SHORAD %s", _group:GetName()) + self:T(text) + local m = MESSAGE:New(text,10,"SHORAD"):ToAllIf(self.debug) + _group:OptionAlarmStateRed() + local groupname = _group:GetName() + if self.ActiveGroups[groupname] == nil then -- no timer yet for this group + self.ActiveGroups[groupname] = { Timing = ActiveTimer } + local endtime = timer.getTime() + (ActiveTimer * math.random(75,100) / 100 ) -- randomize wakeup a bit + timer.scheduleFunction(SleepShorad, _group, endtime) + end + end + end + end + + --- Main function - work on the EventData + -- @param #SHORAD self + -- @param Core.Event#EVENTDATA EventData The event details table data set + function SHORAD:OnEventShot( EventData ) + self:F( { EventData } ) + + --local ShootingUnit = EventData.IniDCSUnit + --local ShootingUnitName = EventData.IniDCSUnitName + local ShootingWeapon = EventData.Weapon -- Identify the weapon fired + local ShootingWeaponName = EventData.WeaponName -- return weapon type + -- get firing coalition + local weaponcoalition = EventData.IniGroup:GetCoalition() + -- get detection probability + if self:_CheckCoalition(weaponcoalition) then --avoid overhead on friendly fire + local IsDetected = self:_ShotIsDetected() + -- convert to text + local DetectedText = "false" + if IsDetected then + DetectedText = "true" + end + local text = string.format("%s Missile Launched = %s | Detected probability state is %s", self.lid, ShootingWeaponName, DetectedText) + self:T( text ) + local m = MESSAGE:New(text,15,"Info"):ToAllIf(self.debug) + -- + if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then + -- get target data + local targetdata = EventData.Weapon:getTarget() -- Identify target + local targetunitname = Unit.getName(targetdata) -- Unit name + local targetgroup = Unit.getGroup(Weapon.getTarget(ShootingWeapon)) --targeted group + local targetgroupname = targetgroup:getName() -- group name + -- check if we or a SAM site are the target + --local TargetGroup = EventData.TgtGroup -- Wrapper.Group#GROUP + local shotatus = self:_CheckShotAtShorad(targetgroupname) --#boolean + local shotatsams = self:_CheckShotAtSams(targetgroupname) --#boolean + -- if being shot at, find closest SHORADs to activate + if shotatsams or shotatus then + self:T({shotatsams=shotatsams,shotatus=shotatus}) + self:WakeUpShorad(targetgroupname, self.Radius, self.ActiveTimer) + end + end + end + end +-- +end +----------------------------------------------------------------------- +-- SHORAD end +----------------------------------------------------------------------- diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 95d211370..decc5b306 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -69,6 +69,7 @@ __Moose.Include( 'Scripts/Moose/Functional/PseudoATC.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Warehouse.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Fox.lua' ) __Moose.Include( 'Scripts/Moose/Functional/Mantis.lua' ) +__Moose.Include( 'Scripts/Moose/Functional/Shorad.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) diff --git a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua index 448d7f545..05bf2d11f 100644 --- a/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua +++ b/Moose Development/Moose/Tasking/Task_A2G_Dispatcher.lua @@ -753,6 +753,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateSEAD( DetectedItem ) -- Returns a SetUnit if there are targets to be SEADed... if TargetSetUnit then Task = TASK_A2G_SEAD:New( Mission, self.SetGroup, string.format( "SEAD.%03d", DetectedItemID ), TargetSetUnit ) + DetectedItem.DesignateMenuName = string.format( "SEAD.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -761,6 +762,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateCAS( DetectedItem ) -- Returns a SetUnit if there are targets to be CASed... if TargetSetUnit then Task = TASK_A2G_CAS:New( Mission, self.SetGroup, string.format( "CAS.%03d", DetectedItemID ), TargetSetUnit ) + DetectedItem.DesignateMenuName = string.format( "CAS.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end @@ -769,6 +771,7 @@ do -- TASK_A2G_DISPATCHER local TargetSetUnit = self:EvaluateBAI( DetectedItem, self.Mission:GetCommandCenter():GetPositionable():GetCoalition() ) -- Returns a SetUnit if there are targets to be BAIed... if TargetSetUnit then Task = TASK_A2G_BAI:New( Mission, self.SetGroup, string.format( "BAI.%03d", DetectedItemID ), TargetSetUnit ) + DetectedItem.DesignateMenuName = string.format( "BAI.%03d", DetectedItemID ) --inject a name for DESIGNATE, if using same DETECTION object Task:SetDetection( Detection, DetectedItem ) end end diff --git a/Moose Development/Moose/Utilities/Enums.lua b/Moose Development/Moose/Utilities/Enums.lua index 84712dc7a..757b67ea1 100644 --- a/Moose Development/Moose/Utilities/Enums.lua +++ b/Moose Development/Moose/Utilities/Enums.lua @@ -310,4 +310,53 @@ ENUMS.Morse.N7="- - * * *" ENUMS.Morse.N8="- - - * *" ENUMS.Morse.N9="- - - - *" ENUMS.Morse.N0="- - - - -" -ENUMS.Morse[" "]=" " \ No newline at end of file +ENUMS.Morse[" "]=" " + +--- ISO (639-1) 2-letter Language Codes. See the [Wikipedia](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). +-- +-- @type ENUMS.ISOLang +ENUMS.ISOLang = +{ + Arabic = 'AR', + Chinese = 'ZH', + English = 'EN', + French = 'FR', + German = 'DE', + Russian = 'RU', + Spanish = 'ES', + Japanese = 'JA', + Italian = 'IT', +} + +--- Phonetic Alphabet (NATO). See the [Wikipedia](https://en.wikipedia.org/wiki/NATO_phonetic_alphabet). +-- +-- @type ENUMS.Phonetic +ENUMS.Phonetic = +{ + A = 'Alpha', + B = 'Bravo', + C = 'Charlie', + D = 'Delta', + E = 'Echo', + F = 'Foxtrot', + G = 'Golf', + H = 'Hotel', + I = 'India', + J = 'Juliett', + K = 'Kilo', + L = 'Lima', + M = 'Mike', + N = 'November', + O = 'Oscar', + P = 'Papa', + Q = 'Quebec', + R = 'Romeo', + S = 'Sierra', + T = 'Tango', + U = 'Uniform', + V = 'Victor', + W = 'Whiskey', + X = 'Xray', + Y = 'Yankee', + Z = 'Zulu', +} \ No newline at end of file diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 2ca10e6f9..adb9cea66 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -97,7 +97,7 @@ CALLSIGN={ JTAC={ Axeman=1, Darknight=2, - Warrier=3, + Warrior=3, Pointer=4, Eyeball=5, Moonbeam=6, diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 57d82d570..da76cfd49 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -68,6 +68,8 @@ Functional/Suppression.lua Functional/PseudoATC.lua Functional/Warehouse.lua Functional/Fox.lua +Functional/Mantis.lua +Functional/Shorad.lua Ops/Airboss.lua Ops/RecoveryTanker.lua