mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
#Changes from Develop
This commit is contained in:
@@ -653,131 +653,132 @@ do -- DETECTION_BASE
|
||||
-- self:T2( { TargetIsDetected = TargetIsDetected, TargetIsVisible = TargetIsVisible, TargetLastTime = TargetLastTime, TargetKnowType = TargetKnowType, TargetKnowDistance = TargetKnowDistance, TargetLastPos = TargetLastPos, TargetLastVelocity = TargetLastVelocity } )
|
||||
|
||||
-- Only process if the target is visible. Detection also returns invisible units.
|
||||
-- if Detection.visible == true then
|
||||
|
||||
local DetectionAccepted = true
|
||||
|
||||
local DetectedObjectName = DetectedObject:getName()
|
||||
local DetectedObjectType = DetectedObject:getTypeName()
|
||||
|
||||
local DetectedObjectVec3 = DetectedObject:getPoint()
|
||||
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
|
||||
local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0}
|
||||
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
|
||||
|
||||
local Distance = ((DetectedObjectVec3.x - DetectionGroupVec3.x) ^ 2 +
|
||||
(DetectedObjectVec3.y - DetectionGroupVec3.y) ^ 2 +
|
||||
(DetectedObjectVec3.z - DetectionGroupVec3.z) ^ 2) ^ 0.5 / 1000
|
||||
|
||||
local DetectedUnitCategory = DetectedObject:getDesc().category
|
||||
|
||||
-- self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } )
|
||||
|
||||
-- Calculate Acceptance
|
||||
|
||||
DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false
|
||||
|
||||
-- if Distance > 15000 then
|
||||
-- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then
|
||||
-- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then
|
||||
-- DetectionAccepted = false
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
if self.AcceptRange and Distance * 1000 > self.AcceptRange then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
|
||||
if self.AcceptZones then
|
||||
local AnyZoneDetection = false
|
||||
for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do
|
||||
local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE
|
||||
if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) then
|
||||
AnyZoneDetection = true
|
||||
--if Detection.visible == true then
|
||||
|
||||
local DetectionAccepted = true
|
||||
|
||||
local DetectedObjectName = DetectedObject:getName()
|
||||
local DetectedObjectType = DetectedObject:getTypeName()
|
||||
|
||||
local DetectedObjectVec3 = DetectedObject:getPoint()
|
||||
local DetectedObjectVec2 = { x = DetectedObjectVec3.x, y = DetectedObjectVec3.z }
|
||||
local DetectionGroupVec3 = Detection:GetVec3() or {x=0,y=0,z=0}
|
||||
local DetectionGroupVec2 = { x = DetectionGroupVec3.x, y = DetectionGroupVec3.z }
|
||||
|
||||
local Distance = ( ( DetectedObjectVec3.x - DetectionGroupVec3.x )^2 +
|
||||
( DetectedObjectVec3.y - DetectionGroupVec3.y )^2 +
|
||||
( DetectedObjectVec3.z - DetectionGroupVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
local DetectedUnitCategory = DetectedObject:getDesc().category
|
||||
|
||||
--self:F( { "Detected Target:", DetectionGroupName, DetectedObjectName, DetectedObjectType, Distance, DetectedUnitCategory } )
|
||||
|
||||
-- Calculate Acceptance
|
||||
|
||||
DetectionAccepted = self._.FilterCategories[DetectedUnitCategory] ~= nil and DetectionAccepted or false
|
||||
|
||||
-- if Distance > 15000 then
|
||||
-- if DetectedUnitCategory == Unit.Category.GROUND_UNIT or DetectedUnitCategory == Unit.Category.SHIP then
|
||||
-- if DetectedObject:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) == false then
|
||||
-- DetectionAccepted = false
|
||||
-- end
|
||||
-- end
|
||||
-- end
|
||||
|
||||
if self.AcceptRange and Distance * 1000 > self.AcceptRange then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
|
||||
if self.AcceptZones then
|
||||
local AnyZoneDetection = false
|
||||
for AcceptZoneID, AcceptZone in pairs( self.AcceptZones ) do
|
||||
local AcceptZone = AcceptZone -- Core.Zone#ZONE_BASE
|
||||
if AcceptZone:IsVec2InZone( DetectedObjectVec2 ) then
|
||||
AnyZoneDetection = true
|
||||
end
|
||||
end
|
||||
if not AnyZoneDetection then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
end
|
||||
if not AnyZoneDetection then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
end
|
||||
|
||||
if self.RejectZones then
|
||||
for RejectZoneID, RejectZone in pairs( self.RejectZones ) do
|
||||
local RejectZone = RejectZone -- Core.Zone#ZONE_BASE
|
||||
if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Calculate additional probabilities
|
||||
|
||||
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.DistanceProbability then
|
||||
local DistanceFactor = Distance / 4
|
||||
local DistanceProbabilityReversed = (1 - self.DistanceProbability) * DistanceFactor
|
||||
local DistanceProbability = 1 - DistanceProbabilityReversed
|
||||
DistanceProbability = DistanceProbability * 30 / 300
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
-- self:T( { Probability, DistanceProbability } )
|
||||
if Probability > DistanceProbability then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
end
|
||||
|
||||
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.AlphaAngleProbability then
|
||||
local NormalVec2 = { x = DetectedObjectVec2.x - DetectionGroupVec2.x, y = DetectedObjectVec2.y - DetectionGroupVec2.y }
|
||||
local AlphaAngle = math.atan2( NormalVec2.y, NormalVec2.x )
|
||||
local Sinus = math.sin( AlphaAngle )
|
||||
local AlphaAngleProbabilityReversed = (1 - self.AlphaAngleProbability) * (1 - Sinus)
|
||||
local AlphaAngleProbability = 1 - AlphaAngleProbabilityReversed
|
||||
|
||||
AlphaAngleProbability = AlphaAngleProbability * 30 / 300
|
||||
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
-- self:T( { Probability, AlphaAngleProbability } )
|
||||
if Probability > AlphaAngleProbability then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.ZoneProbability then
|
||||
|
||||
for ZoneDataID, ZoneData in pairs( self.ZoneProbability ) do
|
||||
self:F( { ZoneData } )
|
||||
local ZoneObject = ZoneData[1] -- Core.Zone#ZONE_BASE
|
||||
local ZoneProbability = ZoneData[2] -- #number
|
||||
ZoneProbability = ZoneProbability * 30 / 300
|
||||
|
||||
if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
-- self:T( { Probability, ZoneProbability } )
|
||||
if Probability > ZoneProbability then
|
||||
|
||||
if self.RejectZones then
|
||||
for RejectZoneID, RejectZone in pairs( self.RejectZones ) do
|
||||
local RejectZone = RejectZone -- Core.Zone#ZONE_BASE
|
||||
if RejectZone:IsVec2InZone( DetectedObjectVec2 ) == true then
|
||||
DetectionAccepted = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if DetectionAccepted then
|
||||
|
||||
HasDetectedObjects = true
|
||||
|
||||
self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {}
|
||||
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
|
||||
|
||||
if TargetIsDetected and TargetIsDetected == true then
|
||||
self.DetectedObjects[DetectedObjectName].IsDetected = TargetIsDetected
|
||||
|
||||
-- Calculate additional probabilities
|
||||
|
||||
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.DistanceProbability then
|
||||
local DistanceFactor = Distance / 4
|
||||
local DistanceProbabilityReversed = ( 1 - self.DistanceProbability ) * DistanceFactor
|
||||
local DistanceProbability = 1 - DistanceProbabilityReversed
|
||||
DistanceProbability = DistanceProbability * 30 / 300
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
--self:T( { Probability, DistanceProbability } )
|
||||
if Probability > DistanceProbability then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
end
|
||||
|
||||
if TargetIsDetected and TargetIsVisible and TargetIsVisible == true then
|
||||
self.DetectedObjects[DetectedObjectName].IsVisible = TargetIsDetected and TargetIsVisible
|
||||
|
||||
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.AlphaAngleProbability then
|
||||
local NormalVec2 = { x = DetectedObjectVec2.x - DetectionGroupVec2.x, y = DetectedObjectVec2.y - DetectionGroupVec2.y }
|
||||
local AlphaAngle = math.atan2( NormalVec2.y, NormalVec2.x )
|
||||
local Sinus = math.sin( AlphaAngle )
|
||||
local AlphaAngleProbabilityReversed = ( 1 - self.AlphaAngleProbability ) * ( 1 - Sinus )
|
||||
local AlphaAngleProbability = 1 - AlphaAngleProbabilityReversed
|
||||
|
||||
AlphaAngleProbability = AlphaAngleProbability * 30 / 300
|
||||
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
--self:T( { Probability, AlphaAngleProbability } )
|
||||
if Probability > AlphaAngleProbability then
|
||||
DetectionAccepted = false
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then
|
||||
self.DetectedObjects[DetectedObjectName].KnowType = TargetIsDetected and TargetKnowType
|
||||
|
||||
if not self.DetectedObjects[DetectedObjectName] and TargetIsVisible and self.ZoneProbability then
|
||||
|
||||
for ZoneDataID, ZoneData in pairs( self.ZoneProbability ) do
|
||||
self:F({ZoneData})
|
||||
local ZoneObject = ZoneData[1] -- Core.Zone#ZONE_BASE
|
||||
local ZoneProbability = ZoneData[2] -- #number
|
||||
ZoneProbability = ZoneProbability * 30 / 300
|
||||
|
||||
if ZoneObject:IsVec2InZone( DetectedObjectVec2 ) == true then
|
||||
local Probability = math.random() -- Selects a number between 0 and 1
|
||||
--self:T( { Probability, ZoneProbability } )
|
||||
if Probability > ZoneProbability then
|
||||
DetectionAccepted = false
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if DetectionAccepted then
|
||||
|
||||
HasDetectedObjects = true
|
||||
|
||||
self.DetectedObjects[DetectedObjectName] = self.DetectedObjects[DetectedObjectName] or {}
|
||||
self.DetectedObjects[DetectedObjectName].Name = DetectedObjectName
|
||||
|
||||
if TargetIsDetected and TargetIsDetected == true then
|
||||
self.DetectedObjects[DetectedObjectName].IsDetected = TargetIsDetected
|
||||
end
|
||||
|
||||
if TargetIsDetected and TargetIsVisible and TargetIsVisible == true then
|
||||
self.DetectedObjects[DetectedObjectName].IsVisible = TargetIsDetected and TargetIsVisible
|
||||
end
|
||||
|
||||
if TargetIsDetected and not self.DetectedObjects[DetectedObjectName].KnowType then
|
||||
self.DetectedObjects[DetectedObjectName].KnowType = TargetIsDetected and TargetKnowType
|
||||
end
|
||||
self.DetectedObjects[DetectedObjectName].KnowDistance = TargetKnowDistance -- Detection.distance -- TargetKnowDistance
|
||||
self.DetectedObjects[DetectedObjectName].LastTime = (TargetIsDetected and TargetIsVisible == false) and TargetLastTime
|
||||
@@ -2439,16 +2440,16 @@ do -- DETECTION_AREAS
|
||||
-- A set with multiple detected zones will be created as there are groups of units detected.
|
||||
--
|
||||
-- ## 4.1) Retrieve the Detected Unit Sets and Detected Zones
|
||||
--
|
||||
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DETECTION_BASE} and
|
||||
--
|
||||
-- The methods to manage the DetectedItems[].Set(s) are implemented in @{Functional.Detection#DECTECTION_BASE} and
|
||||
-- the methods to manage the DetectedItems[].Zone(s) are implemented in @{Functional.Detection#DETECTION_AREAS}.
|
||||
--
|
||||
--
|
||||
-- Retrieve the DetectedItems[].Set with the method @{Functional.Detection#DETECTION_BASE.GetDetectedSet}(). A @{Core.Set#SET_UNIT} object will be returned.
|
||||
--
|
||||
--
|
||||
-- Retrieve the formed @{Zone@ZONE_UNIT}s as a result of the grouping the detected units within the DetectionZoneRange, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZones}().
|
||||
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}().
|
||||
-- To understand the amount of zones created, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneCount}().
|
||||
-- If you want to obtain a specific zone from the DetectedZones, use the method @{Functional.Detection#DETECTION_AREAS.GetDetectionZoneByID}() with a given index.
|
||||
--
|
||||
--
|
||||
-- ## 4.4) Flare or Smoke detected units
|
||||
--
|
||||
-- Use the methods @{Functional.Detection#DETECTION_AREAS.FlareDetectedUnits}() or @{Functional.Detection#DETECTION_AREAS.SmokeDetectedUnits}() to flare or smoke the detected units when a new detection has taken place.
|
||||
@@ -2489,7 +2490,7 @@ do -- DETECTION_AREAS
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
|
||||
--- Retrieve set of detected zones.
|
||||
-- @param #DETECTION_AREAS self
|
||||
-- @return Core.Set#SET_ZONE The @{Set} of ZONE_UNIT objects detected.
|
||||
|
||||
@@ -792,7 +792,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile)
|
||||
local text=string.format("Missile launch detected! Distance %.1f NM, bearing %03d°.", UTILS.MetersToNM(distance), bearing)
|
||||
|
||||
-- Say notching headings.
|
||||
BASE:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
|
||||
self:ScheduleOnce(5, FOX._SayNotchingHeadings, self, player, missile.weapon)
|
||||
|
||||
--TODO: ALERT or INFO depending on whether this is a direct target.
|
||||
--TODO: lauchalertall option.
|
||||
@@ -1114,6 +1114,13 @@ end
|
||||
-- Event Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- FOX event handler for event birth.
|
||||
-- @param #FOX self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function FOX:OnEventPlayerEnterAircraft(EventData)
|
||||
|
||||
end
|
||||
|
||||
--- FOX event handler for event birth.
|
||||
-- @param #FOX self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
@@ -1155,7 +1162,7 @@ function FOX:OnEventBirth(EventData)
|
||||
|
||||
-- Add F10 radio menu for player.
|
||||
if not self.menudisabled then
|
||||
SCHEDULER:New(nil, self._AddF10Commands, {self,_unitName}, 0.1)
|
||||
self:ScheduleOnce(0.1, FOX._AddF10Commands, self, _unitname)
|
||||
end
|
||||
|
||||
-- Player data.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,28 +1,28 @@
|
||||
--- **Functional** -- Train missile defence and deflection.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## Features:
|
||||
--
|
||||
--
|
||||
-- * Track the missiles fired at you and other players, providing bearing and range information of the missiles towards the airplanes.
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range °
|
||||
-- * Provide alerts of missile launches, including detailed information of the units launching, including bearing, range
|
||||
-- * Provide alerts when a missile would have killed your aircraft.
|
||||
-- * Provide alerts when the missile self destructs.
|
||||
-- * Enable / Disable and Configure the Missile Trainer using the various menu options.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ## Missions:
|
||||
--
|
||||
--
|
||||
-- [MIT - Missile Trainer](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/MIT%20-%20Missile%20Trainer)
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- Uses the MOOSE messaging system to be alerted of any missiles fired, and when a missile would hit your aircraft,
|
||||
-- the class will destroy the missile within a certain range, to avoid damage to your aircraft.
|
||||
--
|
||||
--
|
||||
-- When running a mission where the missile trainer is used, the following radio menu structure ( 'Radio Menu' -> 'Other (F10)' -> 'MissileTrainer' ) options are available for the players:
|
||||
--
|
||||
--
|
||||
-- * **Messages**: Menu to configure all messages.
|
||||
-- * **Messages On**: Show all messages.
|
||||
-- * **Messages Off**: Disable all messages.
|
||||
@@ -45,23 +45,23 @@
|
||||
-- * **Range Off**: Disable range information when a missile is fired to a target.
|
||||
-- * **Bearing On**: Shows bearing information when a missile is fired to a target.
|
||||
-- * **Bearing Off**: Disable bearing information when a missile is fired to a target.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **Distance**: Menu to configure the distance when a missile needs to be destroyed when near to a player, during tracking. This will improve/influence hit calculation accuracy, but has the risk of damaging the aircraft when the missile reaches the aircraft before the distance is measured.
|
||||
-- * **50 meter**: Destroys the missile when the distance to the aircraft is below or equal to 50 meter.
|
||||
-- * **100 meter**: Destroys the missile when the distance to the aircraft is below or equal to 100 meter.
|
||||
-- * **150 meter**: Destroys the missile when the distance to the aircraft is below or equal to 150 meter.
|
||||
-- * **200 meter**: Destroys the missile when the distance to the aircraft is below or equal to 200 meter.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
--
|
||||
-- ### Authors: **FlightControl**
|
||||
--
|
||||
--
|
||||
-- ### Contributions:
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
--
|
||||
-- * **Stuka (Danny)**: Who you can search on the Eagle Dynamics Forums. Working together with Danny has resulted in the MISSILETRAINER class.
|
||||
-- Danny has shared his ideas and together we made a design.
|
||||
-- Together with the **476 virtual team**, we tested the MISSILETRAINER class, and got much positive feedback!
|
||||
-- * **132nd Squadron**: Testing and optimizing the logic.
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- @module Functional.MissileTrainer
|
||||
@@ -76,7 +76,7 @@
|
||||
---
|
||||
--
|
||||
-- # Constructor:
|
||||
--
|
||||
--
|
||||
-- Create a new MISSILETRAINER object with the @{#MISSILETRAINER.New} method:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.New}: Creates a new MISSILETRAINER object taking the maximum distance to your aircraft to evaluate when a missile needs to be destroyed.
|
||||
@@ -84,7 +84,7 @@
|
||||
-- MISSILETRAINER will collect each unit declared in the mission with a skill level "Client" and "Player", and will monitor the missiles shot at those.
|
||||
--
|
||||
-- # Initialization:
|
||||
--
|
||||
--
|
||||
-- A MISSILETRAINER object will behave differently based on the usage of initialization methods:
|
||||
--
|
||||
-- * @{#MISSILETRAINER.InitMessagesOnOff}: Sets by default the display of any message to be ON or OFF.
|
||||
@@ -97,8 +97,8 @@
|
||||
-- * @{#MISSILETRAINER.InitRangeOnOff}: Sets by default the display of range information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitBearingOnOff}: Sets by default the display of bearing information of missiles ON of OFF.
|
||||
-- * @{#MISSILETRAINER.InitMenusOnOff}: Allows to configure the options through the radio menu.
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
--
|
||||
-- @field #MISSILETRAINER
|
||||
MISSILETRAINER = {
|
||||
ClassName = "MISSILETRAINER",
|
||||
TrackingMissiles = {},
|
||||
@@ -167,7 +167,7 @@ end
|
||||
-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @param #string Briefing (Optional) Will show a text to the players when starting their mission. Can be used for briefing purposes.
|
||||
-- @return #MISSILETRAINER
|
||||
function MISSILETRAINER:New( Distance, Briefing )
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
@@ -194,8 +194,8 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self:F( "ForEach:" .. Client.UnitName )
|
||||
-- Client:Alive( self._Alive, self )
|
||||
-- end
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
--
|
||||
self.DBClients:ForEachClient(
|
||||
function( Client )
|
||||
self:F( "ForEach:" .. Client.UnitName )
|
||||
Client:Alive( self._Alive, self )
|
||||
@@ -207,9 +207,9 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
-- self.DB:ForEachClient(
|
||||
-- --- @param Wrapper.Client#CLIENT Client
|
||||
-- function( Client )
|
||||
--
|
||||
--
|
||||
-- ... actions ...
|
||||
--
|
||||
--
|
||||
-- end
|
||||
-- )
|
||||
|
||||
@@ -225,7 +225,7 @@ function MISSILETRAINER:New( Distance, Briefing )
|
||||
|
||||
self.DetailsRangeOnOff = true
|
||||
self.DetailsBearingOnOff = true
|
||||
|
||||
|
||||
self.MenusOnOff = true
|
||||
|
||||
self.TrackingMissiles = {}
|
||||
@@ -293,7 +293,7 @@ end
|
||||
--- Increases, decreases the missile tracking message display frequency with the provided time interval in seconds.
|
||||
-- The default frequency is a 3 second interval, so the Tracking Frequency parameter specifies the increase or decrease from the default 3 seconds or the last frequency update.
|
||||
-- @param #MISSILETRAINER self
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @param #number TrackingFrequency Provide a negative or positive value in seconds to incraese or decrease the display frequency.
|
||||
-- @return #MISSILETRAINER self
|
||||
function MISSILETRAINER:InitTrackingFrequency( TrackingFrequency )
|
||||
self:F( TrackingFrequency )
|
||||
@@ -478,30 +478,30 @@ function MISSILETRAINER:OnEventShot( EVentData )
|
||||
if TrainerTargetDCSUnit then
|
||||
local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
|
||||
local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill
|
||||
|
||||
|
||||
self:T(TrainerTargetDCSUnitName )
|
||||
|
||||
|
||||
local Client = self.DBClients:FindClient( TrainerTargetDCSUnitName )
|
||||
if Client then
|
||||
|
||||
|
||||
local TrainerSourceUnit = UNIT:Find( TrainerSourceDCSUnit )
|
||||
local TrainerTargetUnit = UNIT:Find( TrainerTargetDCSUnit )
|
||||
|
||||
|
||||
if self.MessagesOnOff == true and self.AlertsLaunchesOnOff == true then
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched a %s",
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerWeaponName
|
||||
) .. self:_AddRange( Client, TrainerWeapon ) .. self:_AddBearing( Client, TrainerWeapon ), 5, "Launch Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local ClientID = Client:GetID()
|
||||
self:T( ClientID )
|
||||
local MissileData = {}
|
||||
@@ -579,52 +579,52 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
|
||||
-- ALERTS PART
|
||||
|
||||
|
||||
-- Loop for all Player Clients to check the alerts and deletion of missiles.
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
local Client = ClientData.Client
|
||||
|
||||
|
||||
if Client and Client:IsAlive() then
|
||||
|
||||
for MissileDataID, MissileData in pairs( ClientData.MissileData ) do
|
||||
self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
local PositionMissile = TrainerWeapon:getPosition().p
|
||||
local TargetVec3 = Client:GetVec3()
|
||||
|
||||
|
||||
local Distance = ( ( PositionMissile.x - TargetVec3.x )^2 +
|
||||
( PositionMissile.y - TargetVec3.y )^2 +
|
||||
( PositionMissile.z - TargetVec3.z )^2
|
||||
) ^ 0.5 / 1000
|
||||
|
||||
|
||||
if Distance <= self.Distance then
|
||||
-- Hit alert
|
||||
TrainerWeapon:destroy()
|
||||
if self.MessagesOnOff == true and self.AlertsHitsOnOff == true then
|
||||
|
||||
|
||||
self:T( "killed" )
|
||||
|
||||
|
||||
local Message = MESSAGE:New(
|
||||
string.format( "%s launched by %s killed %s",
|
||||
TrainerWeapon:getTypeName(),
|
||||
TrainerSourceUnit:GetTypeName(),
|
||||
TrainerTargetUnit:GetPlayerName()
|
||||
), 15, "Hit Alert" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
Message:ToClient( Client )
|
||||
end
|
||||
|
||||
|
||||
MissileData = nil
|
||||
table.remove( ClientData.MissileData, MissileDataID )
|
||||
self:T(ClientData.MissileData)
|
||||
@@ -639,7 +639,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
TrainerWeaponTypeName,
|
||||
TrainerSourceUnit:GetTypeName()
|
||||
), 5, "Tracking" )
|
||||
|
||||
|
||||
if self.AlertsToAll == true then
|
||||
Message:ToAll()
|
||||
else
|
||||
@@ -660,41 +660,41 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
if ShowMessages == true and self.MessagesOnOff == true and self.TrackingOnOff == true then -- Only do this when tracking information needs to be displayed.
|
||||
|
||||
-- TRACKING PART
|
||||
|
||||
|
||||
-- For the current client, the missile range and bearing details are displayed To the Player Client.
|
||||
-- For the other clients, the missile range and bearing details are displayed To the other Player Clients.
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- To achieve this, a cross loop is done for each Player Client <-> Other Player Client missile information.
|
||||
|
||||
-- Main Player Client loop
|
||||
for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
local Client = ClientData.Client
|
||||
--self:T2( { Client:GetName() } )
|
||||
|
||||
|
||||
|
||||
|
||||
ClientData.MessageToClient = ""
|
||||
ClientData.MessageToAll = ""
|
||||
|
||||
|
||||
-- Other Players Client loop
|
||||
for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do
|
||||
|
||||
|
||||
for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do
|
||||
--self:T3( MissileDataID )
|
||||
|
||||
|
||||
local TrainerSourceUnit = MissileData.TrainerSourceUnit
|
||||
local TrainerWeapon = MissileData.TrainerWeapon
|
||||
local TrainerTargetUnit = MissileData.TrainerTargetUnit
|
||||
local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName
|
||||
local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched
|
||||
|
||||
|
||||
if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then
|
||||
|
||||
|
||||
if ShowMessages == true then
|
||||
local TrackingTo
|
||||
TrackingTo = string.format( " -> %s",
|
||||
TrainerWeaponTypeName
|
||||
)
|
||||
|
||||
|
||||
if ClientDataID == TrackingDataID then
|
||||
if ClientData.MessageToClient == "" then
|
||||
ClientData.MessageToClient = "Missiles to You:\n"
|
||||
@@ -712,7 +712,7 @@ function MISSILETRAINER:_TrackMissiles()
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Once the Player Client and the Other Player Client tracking messages are prepared, show them.
|
||||
if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then
|
||||
local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, 1, "Tracking" ):ToClient( Client )
|
||||
|
||||
@@ -1044,6 +1044,3 @@ function PSEUDOATC:_myname(unitname)
|
||||
|
||||
return string.format("%s (%s)", csign, pname)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -429,6 +429,25 @@ RANGE.TargetType = {
|
||||
-- @field #string airframe Aircraft type of player.
|
||||
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
|
||||
-- @field #string date OS date.
|
||||
-- @field #number attackHdg Attack heading in degrees.
|
||||
-- @field #number attackVel Attack velocity in knots.
|
||||
-- @field #number attackAlt Attack altitude in feet.
|
||||
-- @field #string clock Time of the run.
|
||||
-- @field #string rangename Name of the range.
|
||||
|
||||
--- Strafe result.
|
||||
-- @type RANGE.StrafeResult
|
||||
-- @field #string player Player name.
|
||||
-- @field #string airframe Aircraft type of player.
|
||||
-- @field #number time Time via timer.getAbsTime() in seconds of impact.
|
||||
-- @field #string date OS date.
|
||||
-- @field #string name Name of the target pit.
|
||||
-- @field #number roundsFired Number of rounds fired.
|
||||
-- @field #number roundsHit Number of rounds that hit the target.
|
||||
-- @field #number strafeAccuracy Accuracy of the run in percent.
|
||||
-- @field #string clock Time of the run.
|
||||
-- @field #string rangename Name of the range.
|
||||
-- @field #boolean invalid Invalid pass.
|
||||
|
||||
--- Strafe result.
|
||||
-- @type RANGE.StrafeResult
|
||||
@@ -569,17 +588,16 @@ RANGE.version = "2.4.0"
|
||||
|
||||
--- RANGE contructor. Creates a new RANGE object.
|
||||
-- @param #RANGE self
|
||||
-- @param #string rangename Name of the range. Has to be unique. Will we used to create F10 menu items etc.
|
||||
-- @param #string RangeName Name of the range. Has to be unique. Will we used to create F10 menu items etc.
|
||||
-- @return #RANGE RANGE object.
|
||||
function RANGE:New( rangename )
|
||||
BASE:F( { rangename = rangename } )
|
||||
function RANGE:New( RangeName )
|
||||
|
||||
-- Inherit BASE.
|
||||
local self = BASE:Inherit( self, FSM:New() ) -- #RANGE
|
||||
|
||||
-- Get range name.
|
||||
-- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu.
|
||||
self.rangename = rangename or "Practice Range"
|
||||
self.rangename = RangeName or "Practice Range"
|
||||
|
||||
-- Log id.
|
||||
self.id = string.format( "RANGE %s | ", self.rangename )
|
||||
@@ -950,6 +968,19 @@ function RANGE:SetTargetSheet( path, prefix )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set FunkMan socket. Bombing and strafing results will be send to your Discord bot.
|
||||
-- **Requires running FunkMan program**.
|
||||
-- @param #RANGE self
|
||||
-- @param #number Port Port. Default `10042`.
|
||||
-- @param #string Host Host. Default "127.0.0.1".
|
||||
-- @return #RANGE self
|
||||
function RANGE:SetFunkManOn(Port, Host)
|
||||
|
||||
self.funkmanSocket=SOCKET:New(Port, Host)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set messages to examiner. The examiner will receive messages from all clients.
|
||||
-- @param #RANGE self
|
||||
-- @param #string examinergroupname Name of the group of the examiner.
|
||||
@@ -1728,7 +1759,6 @@ function RANGE:OnEventHit( EventData )
|
||||
self:_DisplayMessageToGroup( _unit, text )
|
||||
self:T2( self.id .. text )
|
||||
_currentTarget.pastfoulline = true
|
||||
invalidStrafe = true -- Rangeboss Edit
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1760,6 +1790,13 @@ function RANGE:OnEventHit( EventData )
|
||||
end
|
||||
end
|
||||
|
||||
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
|
||||
-- @param #RANGE self
|
||||
-- @param #table weapon Weapon
|
||||
function RANGE:_TrackWeapon(weapon)
|
||||
|
||||
end
|
||||
|
||||
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
|
||||
-- @param #RANGE self
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
@@ -1806,6 +1843,11 @@ function RANGE:OnEventShot( EventData )
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
|
||||
|
||||
-- Attack parameters.
|
||||
local attackHdg=_unit:GetHeading()
|
||||
local attackAlt=_unit:GetHeight()
|
||||
local attackVel=_unit:GetVelocityKNOTS()
|
||||
|
||||
-- Set this to larger value than the threshold.
|
||||
local dPR = self.BombtrackThreshold * 2
|
||||
@@ -1848,7 +1890,6 @@ function RANGE:OnEventShot( EventData )
|
||||
|
||||
-- Check again in ~0.005 seconds ==> 200 checks per second.
|
||||
return timer.getTime() + self.dtBombtrack
|
||||
|
||||
else
|
||||
|
||||
-----------------------------
|
||||
@@ -1858,7 +1899,7 @@ function RANGE:OnEventShot( EventData )
|
||||
-- Get closet target to last position.
|
||||
local _closetTarget = nil -- #RANGE.BombTarget
|
||||
local _distance = nil
|
||||
local _closeCoord = nil
|
||||
local _closeCoord = nil --Core.Point#COORDINATE
|
||||
local _hitquality = "POOR"
|
||||
|
||||
-- Get callsign.
|
||||
@@ -1886,6 +1927,7 @@ function RANGE:OnEventShot( EventData )
|
||||
|
||||
-- Loop over defined bombing targets.
|
||||
for _, _bombtarget in pairs( self.bombingTargets ) do
|
||||
local bombtarget=_bombtarget --#RANGE.BombTarget
|
||||
|
||||
-- Get target coordinate.
|
||||
local targetcoord = self:_GetBombTargetCoordinate( _bombtarget )
|
||||
@@ -1898,15 +1940,15 @@ function RANGE:OnEventShot( EventData )
|
||||
-- Find closest target to last known position of the bomb.
|
||||
if _distance == nil or _temp < _distance then
|
||||
_distance = _temp
|
||||
_closetTarget = _bombtarget
|
||||
_closeCoord = targetcoord
|
||||
_closetTarget = bombtarget
|
||||
_closeCoord = targetcoord
|
||||
if _distance <= 1.53 then -- Rangeboss Edit
|
||||
_hitquality = "SHACK" -- Rangeboss Edit
|
||||
elseif _distance <= 0.5 * _bombtarget.goodhitrange then -- Rangeboss Edit
|
||||
elseif _distance <= 0.5 * bombtarget.goodhitrange then -- Rangeboss Edit
|
||||
_hitquality = "EXCELLENT"
|
||||
elseif _distance <= _bombtarget.goodhitrange then
|
||||
elseif _distance <= bombtarget.goodhitrange then
|
||||
_hitquality = "GOOD"
|
||||
elseif _distance <= 2 * _bombtarget.goodhitrange then
|
||||
elseif _distance <= 2 * bombtarget.goodhitrange then
|
||||
_hitquality = "INEFFECTIVE"
|
||||
else
|
||||
_hitquality = "POOR"
|
||||
@@ -1927,6 +1969,7 @@ function RANGE:OnEventShot( EventData )
|
||||
local _results = self.bombPlayerResults[_playername]
|
||||
|
||||
local result = {} -- #RANGE.BombResult
|
||||
result.command=SOCKET.DataType.BOMBRESULT
|
||||
result.name = _closetTarget.name or "unknown"
|
||||
result.distance = _distance
|
||||
result.radial = _closeCoord:HeadingTo( impactcoord )
|
||||
@@ -1934,11 +1977,17 @@ function RANGE:OnEventShot( EventData )
|
||||
result.quality = _hitquality
|
||||
result.player = playerData.playername
|
||||
result.time = timer.getAbsTime()
|
||||
result.clock = UTILS.SecondsToClock(result.time, true)
|
||||
result.midate = UTILS.GetDCSMissionDate()
|
||||
result.theatre = env.mission.theatre
|
||||
result.airframe = playerData.airframe
|
||||
result.roundsFired = 0 -- Rangeboss Edit
|
||||
result.roundsHit = 0 -- Rangeboss Edit
|
||||
result.roundsQuality = "N/A" -- Rangeboss Edit
|
||||
result.rangename = self.rangename
|
||||
result.attackHdg = attackHdg
|
||||
result.attackVel = attackVel
|
||||
result.attackAlt = attackAlt
|
||||
|
||||
-- Add to table.
|
||||
table.insert( _results, result )
|
||||
@@ -2078,13 +2127,13 @@ function RANGE:onafterImpact( From, Event, To, result, player )
|
||||
-- Only display target name if there is more than one bomb target.
|
||||
local targetname = nil
|
||||
if #self.bombingTargets > 1 then
|
||||
local targetname = result.name
|
||||
targetname = result.name
|
||||
end
|
||||
|
||||
-- Send message to player.
|
||||
local text = string.format( "%s, impact %03d° for %d ft", player.playername, result.radial, UTILS.MetersToFeet( result.distance ) )
|
||||
if targetname then
|
||||
text = text .. string.format( " from bulls of target %s." )
|
||||
text = text .. string.format( " from bulls of target %s.", targetname )
|
||||
else
|
||||
text = text .. "."
|
||||
end
|
||||
@@ -2110,16 +2159,40 @@ function RANGE:onafterImpact( From, Event, To, result, player )
|
||||
end
|
||||
|
||||
-- Unit.
|
||||
local unit = UNIT:FindByName( player.unitname )
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( unit, text, nil, true )
|
||||
self:T( self.id .. text )
|
||||
if player.unitname then
|
||||
|
||||
-- Get unit.
|
||||
local unit = UNIT:FindByName( player.unitname )
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( unit, text, nil, true )
|
||||
self:T( self.id .. text )
|
||||
end
|
||||
|
||||
-- Save results.
|
||||
if self.autosave then
|
||||
self:Save()
|
||||
end
|
||||
|
||||
-- Send result to FunkMan, which creates fancy MatLab figures and sends them to Discord via a bot.
|
||||
if self.funkmanSocket then
|
||||
self.funkmanSocket:SendTable(result)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--- Function called after strafing run.
|
||||
-- @param #RANGE self
|
||||
-- @param #string From From state.
|
||||
-- @param #string Event Event.
|
||||
-- @param #string To To state.
|
||||
-- @param #RANGE.PlayerData player Player data table.
|
||||
-- @param #RANGE.StrafeResult result Result of run.
|
||||
function RANGE:onafterStrafeResult( From, Event, To, player, result)
|
||||
|
||||
if self.funkmanSocket then
|
||||
self.funkmanSocket:SendTable(result)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -2175,7 +2248,7 @@ function RANGE:onafterSave( From, Event, To )
|
||||
local target = result.name
|
||||
local radial = result.radial
|
||||
local quality = result.quality
|
||||
local time = UTILS.SecondsToClock( result.time )
|
||||
local time = UTILS.SecondsToClock(result.time, true)
|
||||
local airframe = result.airframe
|
||||
local date = "n/a"
|
||||
if os then
|
||||
@@ -2353,7 +2426,8 @@ end
|
||||
--- Start smoking a coordinate with a delay.
|
||||
-- @param #table _args Argements passed.
|
||||
function RANGE._DelayedSmoke( _args )
|
||||
trigger.action.smoke( _args.coord:GetVec3(), _args.color )
|
||||
_args.coord:Smoke(_args.color)
|
||||
--trigger.action.smoke( _args.coord:GetVec3(), _args.color )
|
||||
end
|
||||
|
||||
--- Display top 10 stafing results of a specific player.
|
||||
@@ -3045,9 +3119,13 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Strafe result.
|
||||
local result = {} -- #RANGE.StrafeResult
|
||||
result.command=SOCKET.DataType.STRAFERESULT
|
||||
result.player=_playername
|
||||
result.name=_result.zone.name or "unknown"
|
||||
result.time = timer.getAbsTime()
|
||||
result.clock = UTILS.SecondsToClock(result.time)
|
||||
result.midate = UTILS.GetDCSMissionDate()
|
||||
result.theatre = env.mission.theatre
|
||||
result.roundsFired = shots
|
||||
result.roundsHit = _result.hits
|
||||
result.roundsQuality = resulttext
|
||||
@@ -3473,6 +3551,7 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display )
|
||||
|
||||
-- Group ID.
|
||||
local _gid = _unit:GetGroup():GetID()
|
||||
local _grp = _unit:GetGroup()
|
||||
|
||||
-- Get playername and player settings
|
||||
local _, playername = self:_GetPlayerUnitAndName( _unit:GetName() )
|
||||
@@ -3480,14 +3559,14 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display )
|
||||
|
||||
-- Send message to player if messages enabled and not only for the examiner.
|
||||
if _gid and (playermessage == true or display) and (not self.examinerexclusive) then
|
||||
trigger.action.outTextForGroup( _gid, _text, _time, _clear )
|
||||
local m = MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit)
|
||||
end
|
||||
|
||||
-- Send message to examiner.
|
||||
if self.examinergroupname ~= nil then
|
||||
local _examinerid = GROUP:FindByName( self.examinergroupname ):GetID()
|
||||
local _examinerid = GROUP:FindByName( self.examinergroupname )
|
||||
if _examinerid then
|
||||
trigger.action.outTextForGroup( _examinerid, _text, _time, _clear )
|
||||
local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_examinerid)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -280,11 +280,11 @@ function SCORING:New( GameName )
|
||||
-- Default fratricide penalty level (maximum penalty that can be assigned to a player before he gets kicked).
|
||||
self:SetFratricide( self.ScaleDestroyPenalty * 3 )
|
||||
self.penaltyonfratricide = true
|
||||
|
||||
|
||||
-- Default penalty when a player changes coalition.
|
||||
self:SetCoalitionChangePenalty( self.ScaleDestroyPenalty )
|
||||
self.penaltyoncoalitionchange = true
|
||||
|
||||
|
||||
self:SetDisplayMessagePrefix()
|
||||
|
||||
-- Event handlers
|
||||
@@ -656,11 +656,12 @@ function SCORING:_AddPlayerFromUnit( UnitData )
|
||||
if self.Players[PlayerName].UnitCoalition ~= UnitCoalition and self.penaltyoncoalitionchange then
|
||||
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + self.CoalitionChangePenalty or 50
|
||||
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] .. "(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). " ..
|
||||
self.CoalitionChangePenalty .. "Penalty points added.",
|
||||
MESSAGE.Type.Information )
|
||||
:ToAll()
|
||||
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1 * self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType, UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
|
||||
MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' changed coalition from " .. _SCORINGCoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. _SCORINGCoalition[UnitCoalition] ..
|
||||
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). ".. self.CoalitionChangePenalty .."Penalty points added.",
|
||||
MESSAGE.Type.Information
|
||||
):ToAll()
|
||||
self:ScoreCSV( PlayerName, "", "COALITION_PENALTY", 1, -1*self.CoalitionChangePenalty, self.Players[PlayerName].UnitName, _SCORINGCoalition[self.Players[PlayerName].UnitCoalition], _SCORINGCategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
|
||||
UnitName, _SCORINGCoalition[UnitCoalition], _SCORINGCategory[UnitCategory], UnitData:GetTypeName() )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -671,7 +672,7 @@ function SCORING:_AddPlayerFromUnit( UnitData )
|
||||
self.Players[PlayerName].UNIT = UnitData
|
||||
self.Players[PlayerName].ThreatLevel = UnitThreatLevel
|
||||
self.Players[PlayerName].ThreatType = UnitThreatType
|
||||
|
||||
|
||||
-- TODO: make fratricide switchable
|
||||
if self.Players[PlayerName].Penalty > self.Fratricide * 0.50 and self.penaltyonfratricide then
|
||||
if self.Players[PlayerName].PenaltyWarning < 1 then
|
||||
@@ -1112,16 +1113,18 @@ 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
|
||||
Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty
|
||||
PlayerHit.Penalty = PlayerHit.Penalty + 10 --* 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,
|
||||
MESSAGE.Type.Update )
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
|
||||
|
||||
MESSAGE
|
||||
:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " ..
|
||||
TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
|
||||
"Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty,
|
||||
MESSAGE.Type.Update
|
||||
)
|
||||
:ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() )
|
||||
:ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() )
|
||||
self:ScoreCSV( Event.WeaponPlayerName, TargetPlayerName, "HIT_PENALTY", 1, -10, Event.WeaponName, Event.WeaponCoalition, Event.WeaponCategory, Event.WeaponTypeName, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
|
||||
else
|
||||
Player.Score = Player.Score + 1
|
||||
@@ -1651,18 +1654,19 @@ function SCORING:ReportScoreGroupDetailed( PlayerGroup )
|
||||
|
||||
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||
|
||||
PlayerMessage = string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty,
|
||||
ReportHits,
|
||||
ReportDestroys,
|
||||
ReportCoalitionChanges,
|
||||
ReportGoals,
|
||||
ReportMissions
|
||||
)
|
||||
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )%s%s%s%s%s",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty,
|
||||
ReportHits,
|
||||
ReportDestroys,
|
||||
ReportCoalitionChanges,
|
||||
ReportGoals,
|
||||
ReportMissions
|
||||
)
|
||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Detailed ):ToGroup( PlayerGroup )
|
||||
end
|
||||
end
|
||||
@@ -1706,13 +1710,14 @@ function SCORING:ReportScoreAllSummary( PlayerGroup )
|
||||
|
||||
local PlayerScore = ScoreHits + ScoreDestroys + ScoreCoalitionChanges + ScoreGoals + ScoreMissions
|
||||
local PlayerPenalty = PenaltyHits + PenaltyDestroys + PenaltyCoalitionChanges + PenaltyGoals + PenaltyMissions
|
||||
|
||||
PlayerMessage = string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty
|
||||
)
|
||||
|
||||
PlayerMessage =
|
||||
string.format( "Player '%s' Score = %d ( %d Score, -%d Penalties )",
|
||||
PlayerName,
|
||||
PlayerScore - PlayerPenalty,
|
||||
PlayerScore,
|
||||
PlayerPenalty
|
||||
)
|
||||
MESSAGE:NewType( PlayerMessage, MESSAGE.Type.Overview ):ToGroup( PlayerGroup )
|
||||
end
|
||||
end
|
||||
|
||||
@@ -79,6 +79,7 @@ SEAD = {
|
||||
["Kh25"] = "Kh25",
|
||||
["BGM_109"] = "BGM_109",
|
||||
["AGM_154"] = "AGM_154",
|
||||
["HY-2"] = "HY-2",
|
||||
}
|
||||
|
||||
--- Missile enumerators - from DCS ME and Wikipedia
|
||||
@@ -98,6 +99,7 @@ SEAD = {
|
||||
["Kh25"] = {25, 0.8},
|
||||
["BGM_109"] = {460, 0.705}, --in-game ~465kn
|
||||
["AGM_154"] = {130, 0.61},
|
||||
["HY-2"] = {90,1},
|
||||
}
|
||||
|
||||
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
-- @module Functional.Shorad
|
||||
-- @image Functional.Shorad.jpg
|
||||
--
|
||||
-- Date: July 2021
|
||||
-- Date: Nov 2021
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **SHORAD** class, extends Core.Base#BASE
|
||||
@@ -41,6 +41,7 @@
|
||||
-- @field #boolean UseEmOnOff Decide if we are using Emission on/off (default) or AlarmState red/green.
|
||||
-- @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
|
||||
@@ -131,6 +132,7 @@ do
|
||||
["Kh29"] = "Kh29",
|
||||
["Kh31"] = "Kh31",
|
||||
["Kh66"] = "Kh66",
|
||||
--["BGM_109"] = "BGM_109",
|
||||
}
|
||||
|
||||
--- Instantiates a new SHORAD object
|
||||
@@ -138,13 +140,13 @@ do
|
||||
-- @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 Radius Defense radius in meters, used to switch on SHORAD groups **within** this radius
|
||||
-- @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"
|
||||
-- @param #boolean UseEmOnOff Use Emissions On/Off rather than Alarm State Red/Green (default: use Emissions switch)
|
||||
-- @retunr #SHORAD self
|
||||
function SHORAD:New(Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition, UseEmOnOff)
|
||||
local self = BASE:Inherit( self, BASE:New() )
|
||||
local self = BASE:Inherit( self, FSM:New() )
|
||||
self:T({Name, ShoradPrefix, Samset, Radius, ActiveTimer, Coalition})
|
||||
|
||||
local GroupSet = SET_GROUP:New():FilterPrefixes(ShoradPrefix):FilterCoalitions(Coalition):FilterCategoryGround():FilterStart()
|
||||
@@ -162,11 +164,17 @@ do
|
||||
self.DefenseLowProb = 70 -- probability to detect a missile shot, low margin
|
||||
self.DefenseHighProb = 90 -- probability to detect a missile shot, high margin
|
||||
self.UseEmOnOff = UseEmOnOff or false -- Decide if we are using Emission on/off (default) or AlarmState red/green
|
||||
self:I("*** SHORAD - Started Version 0.2.8")
|
||||
self:I("*** SHORAD - Started Version 0.3.1")
|
||||
-- Set the string id for output to DCS.log file.
|
||||
self.lid=string.format("SHORAD %s | ", self.name)
|
||||
self:_InitState()
|
||||
self:HandleEvent(EVENTS.Shot, self.HandleEventShot)
|
||||
|
||||
-- Start State.
|
||||
self:SetStartState("Running")
|
||||
self:AddTransition("*", "WakeUpShorad", "*")
|
||||
self:AddTransition("*", "CalculateHitZone", "*")
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -310,7 +318,7 @@ do
|
||||
local hit = false
|
||||
if self.DefendHarms then
|
||||
for _,_name in pairs (SHORAD.Harms) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
@@ -326,7 +334,7 @@ do
|
||||
local hit = false
|
||||
if self.DefendMavs then
|
||||
for _,_name in pairs (SHORAD.Mavs) do
|
||||
if string.find(WeaponName,_name,1) then hit = true end
|
||||
if string.find(WeaponName,_name,1,true) then hit = true end
|
||||
end
|
||||
end
|
||||
return hit
|
||||
@@ -367,7 +375,7 @@ do
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
--_groups:RelocateGroundRandomInRadius(7,100,false,false) -- be a bit evasive
|
||||
end
|
||||
@@ -388,7 +396,7 @@ do
|
||||
local returnname = false
|
||||
for _,_groups in pairs (shoradset) do
|
||||
local groupname = _groups:GetName()
|
||||
if string.find(groupname, tgtgrp, 1) then
|
||||
if string.find(groupname, tgtgrp, 1, true) then
|
||||
returnname = true
|
||||
end
|
||||
end
|
||||
@@ -400,6 +408,7 @@ do
|
||||
-- @return #boolean Returns true for a detection, else false
|
||||
function SHORAD:_ShotIsDetected()
|
||||
self:T(self.lid .. " _ShotIsDetected")
|
||||
if self.debug then return true end
|
||||
local IsDetected = false
|
||||
local DetectionProb = math.random(self.DefenseLowProb, self.DefenseHighProb) -- reference value
|
||||
local ActualDetection = math.random(1,100) -- value for this shot
|
||||
@@ -423,7 +432,7 @@ do
|
||||
-- mymantis = MANTIS:New("BlueMantis","Blue SAM","Blue EWR",nil,"blue",false,"Blue Awacs")
|
||||
-- mymantis:AddShorad(myshorad,720)
|
||||
-- mymantis:Start()
|
||||
function SHORAD:WakeUpShorad(TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
function SHORAD:onafterWakeUpShorad(From, Event, To, TargetGroup, Radius, ActiveTimer, TargetCat)
|
||||
self:T(self.lid .. " WakeUpShorad")
|
||||
self:T({TargetGroup, Radius, ActiveTimer, TargetCat})
|
||||
local targetcat = TargetCat or Object.Category.UNIT
|
||||
@@ -477,6 +486,76 @@ do
|
||||
return self
|
||||
end
|
||||
|
||||
--- (Internal) Calculate hit zone of an AGM-88
|
||||
-- @param #SHORAD self
|
||||
-- @param #table SEADWeapon DCS.Weapon object
|
||||
-- @param Core.Point#COORDINATE pos0 Position of the plane when it fired
|
||||
-- @param #number height Height when the missile was fired
|
||||
-- @param Wrapper.Group#GROUP SEADGroup Attacker group
|
||||
-- @return #SHORAD self
|
||||
function SHORAD:onafterCalculateHitZone(From,Event,To,SEADWeapon,pos0,height,SEADGroup)
|
||||
self:T("**** Calculating hit zone")
|
||||
if SEADWeapon and SEADWeapon:isExist() then
|
||||
--local pos = SEADWeapon:getPoint()
|
||||
|
||||
-- postion and height
|
||||
local position = SEADWeapon:getPosition()
|
||||
local mheight = height
|
||||
-- heading
|
||||
local wph = math.atan2(position.x.z, position.x.x)
|
||||
if wph < 0 then
|
||||
wph=wph+2*math.pi
|
||||
end
|
||||
wph=math.deg(wph)
|
||||
|
||||
-- velocity
|
||||
local wpndata = SEAD.HarmData["AGM_88"]
|
||||
local mveloc = math.floor(wpndata[2] * 340.29)
|
||||
local c1 = (2*mheight*9.81)/(mveloc^2)
|
||||
local c2 = (mveloc^2) / 9.81
|
||||
local Ropt = c2 * math.sqrt(c1+1)
|
||||
if height <= 5000 then
|
||||
Ropt = Ropt * 0.72
|
||||
elseif height <= 7500 then
|
||||
Ropt = Ropt * 0.82
|
||||
elseif height <= 10000 then
|
||||
Ropt = Ropt * 0.87
|
||||
elseif height <= 12500 then
|
||||
Ropt = Ropt * 0.98
|
||||
end
|
||||
|
||||
-- look at a couple of zones across the trajectory
|
||||
for n=1,3 do
|
||||
local dist = Ropt - ((n-1)*20000)
|
||||
local predpos= pos0:Translate(dist,wph)
|
||||
if predpos then
|
||||
|
||||
local targetzone = ZONE_RADIUS:New("Target Zone",predpos:GetVec2(),20000)
|
||||
|
||||
if self.debug then
|
||||
predpos:MarkToAll(string.format("height=%dm | heading=%d | velocity=%ddeg | Ropt=%dm",mheight,wph,mveloc,Ropt),false)
|
||||
targetzone:DrawZone(coalition.side.BLUE,{0,0,1},0.2,nil,nil,3,true)
|
||||
end
|
||||
|
||||
local seadset = self.Groupset
|
||||
local tgtcoord = targetzone:GetRandomPointVec2()
|
||||
local tgtgrp = seadset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local _targetgroup = nil
|
||||
local _targetgroupname = "none"
|
||||
local _targetskill = "Random"
|
||||
if tgtgrp and tgtgrp:IsAlive() then
|
||||
_targetgroup = tgtgrp
|
||||
_targetgroupname = tgtgrp:GetName() -- group name
|
||||
_targetskill = tgtgrp:GetUnit(1):GetSkill()
|
||||
self:T("*** Found Target = ".. _targetgroupname)
|
||||
self:WakeUpShorad(_targetgroupname, self.Radius, self.ActiveTimer, Object.Category.UNIT)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Main function - work on the EventData
|
||||
-- @param #SHORAD self
|
||||
-- @param Core.Event#EVENTDATA EventData The event details table data set
|
||||
@@ -504,13 +583,48 @@ do
|
||||
if (self:_CheckHarms(ShootingWeaponName) or self:_CheckMavs(ShootingWeaponName)) and IsDetected then
|
||||
-- get target data
|
||||
local targetdata = EventData.Weapon:getTarget() -- Identify target
|
||||
-- Is there target data?
|
||||
if not targetdata or self.debug then
|
||||
if string.find(ShootingWeaponName,"AGM_88",1,true) then
|
||||
self:I("**** Tracking AGM-88 with no target data.")
|
||||
local pos0 = EventData.IniUnit:GetCoordinate()
|
||||
local fheight = EventData.IniUnit:GetHeight()
|
||||
self:__CalculateHitZone(20,ShootingWeapon,pos0,fheight,EventData.IniGroup)
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
local targetcat = targetdata:getCategory() -- Identify category
|
||||
self:T(string.format("Target Category (3=STATIC, 1=UNIT)= %s",tostring(targetcat)))
|
||||
self:T({targetdata})
|
||||
local targetunit = nil
|
||||
if targetcat == Object.Category.UNIT then -- UNIT
|
||||
targetunit = UNIT:Find(targetdata)
|
||||
elseif targetcat == Object.Category.STATIC then -- STATIC
|
||||
targetunit = STATIC:Find(targetdata)
|
||||
--self:T("Static Target Data")
|
||||
--self:T({targetdata:isExist()})
|
||||
--self:T({targetdata:getPoint()})
|
||||
local tgtcoord = COORDINATE:NewFromVec3(targetdata:getPoint())
|
||||
--tgtcoord:MarkToAll("Missile Target",true)
|
||||
|
||||
local tgtgrp1 = self.Samset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord1 = tgtgrp1:GetCoordinate()
|
||||
--tgtcoord1:MarkToAll("Close target SAM",true)
|
||||
|
||||
local tgtgrp2 = self.Groupset:FindNearestGroupFromPointVec2(tgtcoord)
|
||||
local tgtcoord2 = tgtgrp2:GetCoordinate()
|
||||
--tgtcoord2:MarkToAll("Close target SHORAD",true)
|
||||
|
||||
local dist1 = tgtcoord:Get2DDistance(tgtcoord1)
|
||||
local dist2 = tgtcoord:Get2DDistance(tgtcoord2)
|
||||
|
||||
if dist1 < dist2 then
|
||||
targetunit = tgtgrp1
|
||||
targetcat = Object.Category.UNIT
|
||||
else
|
||||
targetunit = tgtgrp2
|
||||
targetcat = Object.Category.UNIT
|
||||
end
|
||||
end
|
||||
--local targetunitname = Unit.getName(targetdata) -- Unit name
|
||||
if targetunit and targetunit:IsAlive() then
|
||||
@@ -519,7 +633,11 @@ do
|
||||
local targetgroup = nil
|
||||
local targetgroupname = "none"
|
||||
if targetcat == Object.Category.UNIT then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
if targetunit.ClassName == "UNIT" then
|
||||
targetgroup = targetunit:GetGroup()
|
||||
elseif targetunit.ClassName == "GROUP" then
|
||||
targetgroup = targetunit
|
||||
end
|
||||
targetgroupname = targetgroup:GetName() -- group name
|
||||
elseif targetcat == Object.Category.STATIC then
|
||||
targetgroup = targetunit
|
||||
@@ -540,6 +658,7 @@ do
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
end
|
||||
--
|
||||
end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user