mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a6de09a0ca | ||
|
|
4668132b37 | ||
|
|
ceb77e2837 | ||
|
|
137f0251fb | ||
|
|
16dc3860f7 | ||
|
|
333ed629bb | ||
|
|
c87e91d845 | ||
|
|
778ae1b8e5 | ||
|
|
6df4fffafd | ||
|
|
783e29f189 | ||
|
|
af39a3ae9c | ||
|
|
c985d40ca0 | ||
|
|
90b588420f | ||
|
|
3a0d2a5c51 | ||
|
|
07a76ced88 | ||
|
|
a3805118a0 | ||
|
|
2e6957984f | ||
|
|
433a66d530 | ||
|
|
7d3ad15f39 | ||
|
|
d0728afee7 | ||
|
|
01330bf00c | ||
|
|
01de638b8e | ||
|
|
3e8c7ad1df | ||
|
|
22c6a03161 | ||
|
|
dd8b2caa24 | ||
|
|
1e4cfd473c | ||
|
|
044fb66ca0 |
@@ -1301,7 +1301,7 @@ function EVENT:onEvent( Event )
|
||||
-- STATIC
|
||||
---
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
if Event.target.isExist and Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object, check that isExist exists (Kiowa Hellfire issue, Special K)
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
-- Workaround for borked target info on cruise missiles
|
||||
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -509,10 +509,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
|
||||
end
|
||||
|
||||
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE"
|
||||
_MESSAGESRS.MSRS:SetLabel(Label or "MESSAGE")
|
||||
_MESSAGESRS.MSRS:SetLabel(_MESSAGESRS.label)
|
||||
|
||||
_MESSAGESRS.port = Port or MSRS.port or 5002
|
||||
_MESSAGESRS.MSRS:SetPort(Port or 5002)
|
||||
_MESSAGESRS.MSRS:SetPort(_MESSAGESRS.port)
|
||||
|
||||
_MESSAGESRS.volume = Volume or MSRS.volume or 1
|
||||
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)
|
||||
|
||||
@@ -1516,6 +1516,7 @@ do
|
||||
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
|
||||
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash )
|
||||
if self.Filter.Zones then
|
||||
self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self)
|
||||
local timing = self.ZoneTimerInterval or 30
|
||||
@@ -3477,7 +3478,7 @@ do -- SET_STATIC
|
||||
|
||||
--- Add STATIC(s) to SET_STATIC.
|
||||
-- @param #SET_STATIC self
|
||||
-- @param #string AddStatic A single STATIC.
|
||||
-- @param Wrapper.Static#STATIC AddStatic A single STATIC.
|
||||
-- @return #SET_STATIC self
|
||||
function SET_STATIC:AddStatic( AddStatic )
|
||||
self:F2( AddStatic:GetName() )
|
||||
|
||||
@@ -202,19 +202,19 @@
|
||||
--
|
||||
-- ### Link-16 Datalink STN and SADL IDs (limited at the moment to F15/16/18/AWACS/Tanker/B1B, but not the F15E for clients, SADL A10CII only)
|
||||
--
|
||||
-- *{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
|
||||
-- *{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
|
||||
-- * @{#SPAWN.InitSTN}(): Set the STN of the first unit in the group. All other units will have consecutive STNs, provided they have not been used yet.
|
||||
-- * @{#SPAWN.InitSADL}(): Set the SADL of the first unit in the group. All other units will have consecutive SADLs, provided they have not been used yet.
|
||||
--
|
||||
-- ### Callsigns
|
||||
--
|
||||
-- *{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
|
||||
-- *{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
|
||||
-- * @{#SPAWN.InitRandomizeCallsign}(): Set a random callsign name per spawn.
|
||||
-- * @{#SPAWN.SpawnInitCallSign}(): Set a specific callsign for a spawned group.
|
||||
--
|
||||
-- ### Speed
|
||||
--
|
||||
-- *{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
|
||||
-- *{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
|
||||
-- *{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
|
||||
-- * @{#SPAWN.InitSpeedMps}(): Set the initial speed on spawning in meters per second.
|
||||
-- * @{#SPAWN.InitSpeedKph}(): Set the initial speed on spawning in kilometers per hour.
|
||||
-- * @{#SPAWN.InitSpeedKnots}(): Set the initial speed on spawning in knots.
|
||||
--
|
||||
-- ## SPAWN **Spawn** methods
|
||||
--
|
||||
@@ -292,9 +292,10 @@ SPAWN = {
|
||||
|
||||
--- Enumerator for spawns at airbases
|
||||
-- @type SPAWN.Takeoff
|
||||
-- @extends Wrapper.Group#GROUP.Takeoff
|
||||
|
||||
-- @field #SPAWN.Takeoff Takeoff
|
||||
-- @field #number Air Take off happens in air.
|
||||
-- @field #number Runway Spawn on runway. Does not work in MP!
|
||||
-- @field #number Hot Spawn at parking with engines on.
|
||||
-- @field #number Cold Spawn at parking with engines off.
|
||||
SPAWN.Takeoff = {
|
||||
Air = 1,
|
||||
Runway = 2,
|
||||
@@ -619,12 +620,14 @@ end
|
||||
-- and any spaces before and after the resulting name are removed.
|
||||
-- IMPORTANT! This method MUST be the first used after :New !!!
|
||||
-- @param #SPAWN self
|
||||
-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided to make new unit names.
|
||||
-- @param #boolean KeepUnitNames (optional) If true, the unit names are kept, false or not provided create new unit names.
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitKeepUnitNames( KeepUnitNames )
|
||||
self:F()
|
||||
|
||||
self.SpawnInitKeepUnitNames = KeepUnitNames or true
|
||||
self.SpawnInitKeepUnitNames = false
|
||||
|
||||
if KeepUnitNames == true then self.SpawnInitKeepUnitNames = true end
|
||||
|
||||
return self
|
||||
end
|
||||
@@ -1208,11 +1211,12 @@ end
|
||||
-- @param #number Major Major number, i.e. the group number of this name, e.g. 1 - resulting in e.g. Texaco-2-1
|
||||
-- @return #SPAWN self
|
||||
function SPAWN:InitCallSign(ID,Name,Minor,Major)
|
||||
local Name = Name or "Enfield"
|
||||
self.SpawnInitCallSign = true
|
||||
self.SpawnInitCallSignID = ID or 1
|
||||
self.SpawnInitCallSignMinor = Minor or 1
|
||||
self.SpawnInitCallSignMajor = Major or 1
|
||||
self.SpawnInitCallSignName = string.lower(Name) or "enfield"
|
||||
self.SpawnInitCallSignName=string.lower(Name):gsub("^%l", string.upper)
|
||||
return self
|
||||
end
|
||||
|
||||
@@ -1608,8 +1612,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
|
||||
RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
|
||||
numTries = numTries + 1
|
||||
inZone = SpawnZone:IsVec2InZone(RandomVec2)
|
||||
self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
|
||||
self:I(SpawnZone)
|
||||
--self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
|
||||
--self:I(SpawnZone)
|
||||
end
|
||||
end
|
||||
if (not inZone) then
|
||||
@@ -3275,7 +3279,7 @@ end
|
||||
--- Get the index from a given group.
|
||||
-- The function will search the name of the group for a #, and will return the number behind the #-mark.
|
||||
function SPAWN:GetSpawnIndexFromGroup( SpawnGroup )
|
||||
self:F2( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
|
||||
self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnGroup } )
|
||||
|
||||
local IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
|
||||
local Index = tonumber( IndexString )
|
||||
@@ -3287,7 +3291,7 @@ end
|
||||
|
||||
--- Return the last maximum index that can be used.
|
||||
function SPAWN:_GetLastIndex()
|
||||
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
|
||||
self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
|
||||
|
||||
return self.SpawnMaxGroups
|
||||
end
|
||||
@@ -3435,24 +3439,28 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
|
||||
end
|
||||
|
||||
if self.SpawnInitKeepUnitNames == false then
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
if not string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
|
||||
SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID )
|
||||
end
|
||||
SpawnTemplate.units[UnitID].unitId = nil
|
||||
end
|
||||
else
|
||||
for UnitID = 1, #SpawnTemplate.units do
|
||||
local SpawnInitKeepUnitIFF = false
|
||||
if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
|
||||
SpawnInitKeepUnitIFF = true
|
||||
end
|
||||
local UnitPrefix, Rest
|
||||
if SpawnInitKeepUnitIFF == false then
|
||||
UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
|
||||
self:T( { UnitPrefix, Rest } )
|
||||
else
|
||||
UnitPrefix=SpawnTemplate.units[UnitID].name
|
||||
local SpawnInitKeepUnitIFF = false
|
||||
if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
|
||||
SpawnInitKeepUnitIFF = true
|
||||
end
|
||||
SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
|
||||
local UnitPrefix, Rest
|
||||
if SpawnInitKeepUnitIFF == false then
|
||||
UnitPrefix, Rest = string.match( SpawnTemplate.units[UnitID].name, "^([^#]+)#?" ):gsub( "^%s*(.-)%s*$", "%1" )
|
||||
SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
|
||||
self:T( { UnitPrefix, Rest } )
|
||||
--else
|
||||
--UnitPrefix=SpawnTemplate.units[UnitID].name
|
||||
end
|
||||
--SpawnTemplate.units[UnitID].name = string.format( '%s#%03d-%02d', UnitPrefix, SpawnIndex, UnitID )
|
||||
|
||||
SpawnTemplate.units[UnitID].unitId = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3076,9 +3076,25 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName)
|
||||
-- points for the drawings are saved in local space, so add the object's map x and y coordinates to get
|
||||
-- world space points we can use
|
||||
for _, point in UTILS.spairs(object["points"]) do
|
||||
-- check if we want to skip adding a point
|
||||
local skip = false
|
||||
local p = {x = object["mapX"] + point["x"],
|
||||
y = object["mapY"] + point["y"] }
|
||||
table.add(points, p)
|
||||
|
||||
-- Check if the same coordinates already exist in the list, skip if they do
|
||||
-- This can happen when drawing a Polygon in Free mode, DCS adds points on
|
||||
-- top of each other that are in the `mission` file, but not visible in the
|
||||
-- Mission Editor
|
||||
for _, pt in pairs(points) do
|
||||
if pt.x == p.x and pt.y == p.y then
|
||||
skip = true
|
||||
end
|
||||
end
|
||||
|
||||
-- if it's a unique point, add it
|
||||
if not skip then
|
||||
table.add(points, p)
|
||||
end
|
||||
end
|
||||
elseif object["polygonMode"] == "rect" then
|
||||
-- the points for a rect are saved as local coordinates with an angle. To get the world space points from this
|
||||
@@ -3096,6 +3112,7 @@ function ZONE_POLYGON:NewFromDrawing(DrawingName)
|
||||
|
||||
points = {p1, p2, p3, p4}
|
||||
else
|
||||
-- bring the Arrow code over from Shape/Polygon
|
||||
-- something else that might be added in the future
|
||||
end
|
||||
end
|
||||
|
||||
@@ -184,7 +184,7 @@
|
||||
|
||||
do -- DESIGNATE
|
||||
|
||||
--- @type DESIGNATE
|
||||
-- @type DESIGNATE
|
||||
-- @extends Core.Fsm#FSM_PROCESS
|
||||
|
||||
--- Manage the designation of detected targets.
|
||||
@@ -525,7 +525,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
self.FlashStatusMenu[AttackGroup] = FlashMenu
|
||||
end
|
||||
@@ -554,7 +554,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
self.FlashDetectionMessage[AttackGroup] = FlashDetectionMessage
|
||||
end
|
||||
@@ -826,7 +826,7 @@ do -- DESIGNATE
|
||||
-- This Detection is obsolete, remove from the designate scope
|
||||
self.Designating[DesignateIndex] = nil
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
--- @param Wrapper.Group#GROUP AttackGroup
|
||||
-- @param Wrapper.Group#GROUP AttackGroup
|
||||
function( AttackGroup )
|
||||
if AttackGroup:IsAlive() == true then
|
||||
local DetectionText = self.Detection:DetectedItemReportSummary( DetectedItem, AttackGroup ):Text( ", " )
|
||||
@@ -903,7 +903,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
--- @param Wrapper.Group#GROUP GroupReport
|
||||
-- @param Wrapper.Group#GROUP GroupReport
|
||||
function( AttackGroup )
|
||||
|
||||
if self.FlashStatusMenu[AttackGroup] or ( MenuAttackGroup and ( AttackGroup:GetName() == MenuAttackGroup:GetName() ) ) then
|
||||
@@ -1060,7 +1060,7 @@ do -- DESIGNATE
|
||||
|
||||
self.AttackSet:ForEachGroupAlive(
|
||||
|
||||
--- @param Wrapper.Group#GROUP GroupReport
|
||||
-- @param Wrapper.Group#GROUP GroupReport
|
||||
function( AttackGroup )
|
||||
|
||||
self:ScheduleOnce( Delay, self.SetMenu, self, AttackGroup )
|
||||
@@ -1198,7 +1198,7 @@ do -- DESIGNATE
|
||||
--local ReportTypes = REPORT:New()
|
||||
--local ReportLaserCodes = REPORT:New()
|
||||
|
||||
TargetSetUnit:Flush( self )
|
||||
--TargetSetUnit:Flush( self )
|
||||
|
||||
--self:F( { Recces = self.Recces } )
|
||||
for TargetUnit, RecceData in pairs( self.Recces ) do
|
||||
@@ -1229,10 +1229,12 @@ do -- DESIGNATE
|
||||
end
|
||||
end
|
||||
|
||||
if TargetSetUnit == nil then return end
|
||||
|
||||
if self.AutoLase or ( not self.AutoLase and ( self.LaseStart + Duration >= timer.getTime() ) ) then
|
||||
|
||||
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
-- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( TargetUnit )
|
||||
|
||||
self:F( { TargetUnit = TargetUnit:GetName() } )
|
||||
@@ -1253,7 +1255,7 @@ do -- DESIGNATE
|
||||
|
||||
local RecceUnit = UnitData -- Wrapper.Unit#UNIT
|
||||
local RecceUnitDesc = RecceUnit:GetDesc()
|
||||
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )
|
||||
--self:F( { RecceUnit = RecceUnit:GetName(), RecceDescription = RecceUnitDesc } )x
|
||||
|
||||
if RecceUnit:IsLasing() == false then
|
||||
--self:F( { IsDetected = RecceUnit:IsDetected( TargetUnit ), IsLOS = RecceUnit:IsLOS( TargetUnit ) } )
|
||||
@@ -1275,9 +1277,10 @@ do -- DESIGNATE
|
||||
local Spot = RecceUnit:LaseUnit( TargetUnit, LaserCode, Duration )
|
||||
local AttackSet = self.AttackSet
|
||||
local DesignateName = self.DesignateName
|
||||
local typename = TargetUnit:GetTypeName()
|
||||
|
||||
function Spot:OnAfterDestroyed( From, Event, To )
|
||||
self.Recce:MessageToSetGroup( "Target " .. TargetUnit:GetTypeName() .. " destroyed. " .. TargetSetUnit:Count() .. " targets left.",
|
||||
self.Recce:MessageToSetGroup( "Target " ..typename .. " destroyed. " .. TargetSetUnit:CountAlive() .. " targets left.",
|
||||
5, AttackSet, self.DesignateName )
|
||||
end
|
||||
|
||||
@@ -1285,7 +1288,7 @@ do -- DESIGNATE
|
||||
-- OK. We have assigned for the Recce a TargetUnit. We can exit the function.
|
||||
MarkingCount = MarkingCount + 1
|
||||
local TargetUnitType = TargetUnit:GetTypeName()
|
||||
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnit:GetTypeName() .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
RecceUnit:MessageToSetGroup( "Marking " .. TargetUnitType .. " with laser " .. RecceUnit:GetSpot().LaserCode .. " for " .. Duration .. "s.",
|
||||
10, self.AttackSet, DesignateName )
|
||||
if not MarkedTypes[TargetUnitType] then
|
||||
MarkedTypes[TargetUnitType] = true
|
||||
@@ -1392,7 +1395,7 @@ do -- DESIGNATE
|
||||
local MarkedCount = 0
|
||||
|
||||
TargetSetUnit:ForEachUnitPerThreatLevel( 10, 0,
|
||||
--- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
-- @param Wrapper.Unit#UNIT SmokeUnit
|
||||
function( SmokeUnit )
|
||||
|
||||
if MarkedCount < self.MaximumMarkings then
|
||||
@@ -1457,9 +1460,10 @@ do -- DESIGNATE
|
||||
-- @param #DESIGNATE self
|
||||
-- @return #DESIGNATE
|
||||
function DESIGNATE:onafterDoneSmoking( From, Event, To, Index )
|
||||
|
||||
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
|
||||
self:SetDesignateMenu()
|
||||
if self.Designating[Index] ~= nil then
|
||||
self.Designating[Index] = string.gsub( self.Designating[Index], "S", "" )
|
||||
self:SetDesignateMenu()
|
||||
end
|
||||
end
|
||||
|
||||
--- DoneIlluminating
|
||||
@@ -1472,5 +1476,3 @@ do -- DESIGNATE
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
@@ -545,7 +545,7 @@ do -- DETECTION_BASE
|
||||
-- @param #string To The To State string.
|
||||
function DETECTION_BASE:onafterDetect( From, Event, To )
|
||||
|
||||
local DetectDelay = 0.1
|
||||
local DetectDelay = 0.15
|
||||
self.DetectionCount = 0
|
||||
self.DetectionRun = 0
|
||||
self:UnIdentifyAllDetectedObjects() -- Resets the DetectedObjectsIdentified table
|
||||
@@ -604,7 +604,7 @@ do -- DETECTION_BASE
|
||||
-- @param #number DetectionTimeStamp Time stamp of detection event.
|
||||
function DETECTION_BASE:onafterDetection( From, Event, To, Detection, DetectionTimeStamp )
|
||||
|
||||
-- self:F( { DetectedObjects = self.DetectedObjects } )
|
||||
self:I( { DetectedObjects = self.DetectedObjects } )
|
||||
|
||||
self.DetectionRun = self.DetectionRun + 1
|
||||
|
||||
@@ -612,14 +612,14 @@ do -- DETECTION_BASE
|
||||
|
||||
if Detection and Detection:IsAlive() then
|
||||
|
||||
-- self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } )
|
||||
self:I( { "DetectionGroup is Alive", Detection:GetName() } )
|
||||
|
||||
local DetectionGroupName = Detection:GetName()
|
||||
local DetectionUnit = Detection:GetUnit( 1 )
|
||||
|
||||
local DetectedUnits = {}
|
||||
|
||||
local DetectedTargets = Detection:GetDetectedTargets(
|
||||
local DetectedTargets = DetectionUnit:GetDetectedTargets(
|
||||
self.DetectVisual,
|
||||
self.DetectOptical,
|
||||
self.DetectRadar,
|
||||
@@ -628,8 +628,10 @@ do -- DETECTION_BASE
|
||||
self.DetectDLINK
|
||||
)
|
||||
|
||||
self:F( { DetectedTargets = DetectedTargets } )
|
||||
|
||||
--self:I( { DetectedTargets = DetectedTargets } )
|
||||
--self:I(UTILS.PrintTableToLog(DetectedTargets))
|
||||
|
||||
|
||||
for DetectionObjectID, Detection in pairs( DetectedTargets ) do
|
||||
local DetectedObject = Detection.object -- DCS#Object
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
-- @module Functional.Mantis
|
||||
-- @image Functional.Mantis.jpg
|
||||
--
|
||||
-- Last Update: Feb 2024
|
||||
-- Last Update: May 2024
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
--- **MANTIS** class, extends Core.Base#BASE
|
||||
@@ -58,6 +58,7 @@
|
||||
-- @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
|
||||
-- @field #number ShoradActDistance Distance of an attacker in meters from a Mantis SAM site, on which Shorad will be switched on. Useful to not give away Shorad sites too early. Default 15km. Should be smaller than checkradius.
|
||||
-- @field #boolean checkforfriendlies If true, do not activate a SAM installation if a friendly aircraft is in firing range.
|
||||
-- @extends Core.Base#BASE
|
||||
|
||||
|
||||
@@ -187,29 +188,34 @@
|
||||
-- -- This is effectively a 3-stage filter allowing for zone overlap. A coordinate is accepted first when
|
||||
-- -- it is inside any AcceptZone. Then RejectZones are checked, which enforces both borders, but also overlaps of
|
||||
-- -- Accept- and RejectZones. Last, if it is inside a conflict zone, it is accepted.
|
||||
-- `mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)`
|
||||
-- mybluemantis:AddZones(AcceptZones,RejectZones,ConflictZones)
|
||||
--
|
||||
--
|
||||
-- ### 2.1.2 Change the number of long-, mid- and short-range systems going live on a detected target:
|
||||
--
|
||||
-- -- parameters are numbers. Defaults are 1,2,2,6 respectively
|
||||
-- `mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)`
|
||||
-- mybluemantis:SetMaxActiveSAMs(Short,Mid,Long,Classic)
|
||||
--
|
||||
-- ### 2.1.3 SHORAD will automatically be added from SAM sites of type "short-range"
|
||||
--
|
||||
-- ### 2.1.4 Advanced features
|
||||
--
|
||||
-- -- switch off auto mode **before** you start MANTIS.
|
||||
-- `mybluemantis.automode = false`
|
||||
-- mybluemantis.automode = false
|
||||
--
|
||||
-- -- switch off auto shorad **before** you start MANTIS.
|
||||
-- `mybluemantis.autoshorad = false`
|
||||
-- mybluemantis.autoshorad = false
|
||||
--
|
||||
-- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
|
||||
-- -- also see engagerange below.
|
||||
-- ` self.radiusscale[MANTIS.SamType.LONG] = 1.1`
|
||||
-- ` self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2`
|
||||
-- ` self.radiusscale[MANTIS.SamType.SHORT] = 1.3`
|
||||
-- self.radiusscale[MANTIS.SamType.LONG] = 1.1
|
||||
-- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
|
||||
-- self.radiusscale[MANTIS.SamType.SHORT] = 1.3
|
||||
--
|
||||
-- ### 2.1.5 Friendlies check in firing range
|
||||
--
|
||||
-- -- For some scenarios, like Cold War, it might be useful not to activate SAMs if friendly aircraft are around to avoid death by friendly fire.
|
||||
-- mybluemantis.checkforfriendlies = true
|
||||
--
|
||||
-- # 3. Default settings [both modes unless stated otherwise]
|
||||
--
|
||||
@@ -321,6 +327,7 @@ MANTIS = {
|
||||
automode = true,
|
||||
autoshorad = true,
|
||||
ShoradGroupSet = nil,
|
||||
checkforfriendlies = false,
|
||||
}
|
||||
|
||||
--- Advanced state enumerator
|
||||
@@ -502,7 +509,8 @@ do
|
||||
-- DONE: Treat Awacs separately, since they might be >80km off site
|
||||
-- DONE: Allow tables of prefixes for the setup
|
||||
-- DONE: Auto-Mode with range setups for various known SAM types.
|
||||
|
||||
|
||||
self.name = name or "mymantis"
|
||||
self.SAM_Templates_Prefix = samprefix or "Red SAM"
|
||||
self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
|
||||
self.HQ_Template_CC = hq or nil
|
||||
@@ -631,7 +639,7 @@ do
|
||||
|
||||
-- TODO Version
|
||||
-- @field #string version
|
||||
self.version="0.8.16"
|
||||
self.version="0.8.18"
|
||||
self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
|
||||
|
||||
--- FSM Functions ---
|
||||
@@ -1222,10 +1230,10 @@ do
|
||||
function MANTIS:_PreFilterHeight(height)
|
||||
self:T(self.lid.."_PreFilterHeight")
|
||||
local set = {}
|
||||
local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
|
||||
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
||||
local detectedgroups = dlink:GetContactTable()
|
||||
for _,_contact in pairs(detectedgroups) do
|
||||
local contact = _contact -- Ops.Intelligence#INTEL.Contact
|
||||
local contact = _contact -- Ops.Intel#INTEL.Contact
|
||||
local grp = contact.group -- Wrapper.Group#GROUP
|
||||
if grp:IsAlive() then
|
||||
if grp:GetHeight(true) < height then
|
||||
@@ -1255,6 +1263,10 @@ do
|
||||
-- DEBUG
|
||||
set = self:_PreFilterHeight(height)
|
||||
end
|
||||
local friendlyset -- Core.Set#SET_GROUP
|
||||
if self.checkforfriendlies == true then
|
||||
friendlyset = SET_GROUP:New():FilterCoalitions(self.Coalition):FilterCategories({"plane","helicopter"}):FilterFunction(function(grp) if grp and grp:InAir() then return true else return false end end):FilterOnce()
|
||||
end
|
||||
for _,_coord in pairs (set) do
|
||||
local coord = _coord -- get current coord to check
|
||||
-- output for cross-check
|
||||
@@ -1279,8 +1291,16 @@ do
|
||||
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
|
||||
self:T(self.lid..text)
|
||||
end
|
||||
-- friendlies around?
|
||||
local nofriendlies = true
|
||||
if self.checkforfriendlies == true then
|
||||
local closestfriend, distance = friendlyset:GetClosestGroup(samcoordinate)
|
||||
if closestfriend and distance and distance < rad then
|
||||
nofriendlies = false
|
||||
end
|
||||
end
|
||||
-- end output to cross-check
|
||||
if targetdistance <= rad and zonecheck then
|
||||
if targetdistance <= rad and zonecheck == true and nofriendlies == true then
|
||||
return true, targetdistance
|
||||
end
|
||||
end
|
||||
@@ -1777,7 +1797,7 @@ do
|
||||
-- @return #MANTIS self
|
||||
function MANTIS:_CheckDLinkState()
|
||||
self:T(self.lid .. "_CheckDLinkState")
|
||||
local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK
|
||||
local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
|
||||
local TS = timer.getAbsTime()
|
||||
if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then
|
||||
self.DLink = false
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
-- Implementation is based on the [Simple Range Script](https://forums.eagle.ru/showthread.php?t=157991) by Ciribob, which itself was motivated
|
||||
-- by a script by SNAFU [see here](https://forums.eagle.ru/showthread.php?t=109174).
|
||||
--
|
||||
-- [476th - Air Weapons Range Objects mod](http://www.476vfightergroup.com/downloads.php?do=file&id=287) is highly recommended for this class.
|
||||
-- [476th - Air Weapons Range Objects mod](https://www.476vfightergroup.com/downloads.php?do=download&downloadid=482) is highly recommended for this class.
|
||||
--
|
||||
-- **Main Features:**
|
||||
--
|
||||
@@ -46,7 +46,7 @@
|
||||
--
|
||||
-- ### Contributions: FlightControl, Ciribob
|
||||
-- ### SRS Additions: Applevangelist
|
||||
--
|
||||
--
|
||||
-- ===
|
||||
-- @module Functional.Range
|
||||
-- @image Range.JPG
|
||||
@@ -102,7 +102,7 @@
|
||||
-- @field #string targetpath Path where to save the target sheets.
|
||||
-- @field #string targetprefix File prefix for target sheet files.
|
||||
-- @field Sound.SRS#MSRS controlmsrs SRS wrapper for range controller.
|
||||
-- @field Sound.SRS#MSRSQUEUE controlsrsQ SRS queue for range controller.
|
||||
-- @field Sound.SRS#MSRSQUEUE controlsrsQ SRS queue for range controller.
|
||||
-- @field Sound.SRS#MSRS instructmsrs SRS wrapper for range instructor.
|
||||
-- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor.
|
||||
-- @field #number Coalition Coalition side for the menu, if any.
|
||||
@@ -169,7 +169,7 @@
|
||||
--
|
||||
-- ## Specifying Coordinates
|
||||
--
|
||||
-- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified
|
||||
-- It is also possible to specify coordinates rather than unit or static objects as bombing target locations. This has the advantage, that even when the unit/static object is dead, the specified
|
||||
-- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function.
|
||||
--
|
||||
-- # Fine Tuning
|
||||
@@ -231,8 +231,8 @@
|
||||
-- By default, the sound files are placed in the "Range Soundfiles/" folder inside the mission (.miz) file. Another folder can be specified via the @{#RANGE.SetSoundfilesPath}(*path*) function.
|
||||
--
|
||||
-- ## Voice output via SRS
|
||||
--
|
||||
-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}().
|
||||
--
|
||||
-- Alternatively, the voice output can be fully done via SRS, **no sound file additions needed**. Set up SRS with @{#RANGE.SetSRS}().
|
||||
-- Range control and instructor frequencies and voices can then be set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}().
|
||||
--
|
||||
-- # Persistence
|
||||
@@ -243,11 +243,11 @@
|
||||
-- The next time you start the mission, these results are also automatically loaded.
|
||||
--
|
||||
-- Strafing results are currently **not** saved.
|
||||
--
|
||||
--
|
||||
-- # FSM Events
|
||||
--
|
||||
--
|
||||
-- This class creates additional events that can be used by mission designers for custom reactions
|
||||
--
|
||||
--
|
||||
-- * `EnterRange` when a player enters a range zone. See @{#RANGE.OnAfterEnterRange}
|
||||
-- * `ExitRange` when a player leaves a range zone. See @{#RANGE.OnAfterExitRange}
|
||||
-- * `Impact` on impact of a player's weapon on a bombing target. See @{#RANGE.OnAfterImpact}
|
||||
@@ -371,7 +371,7 @@ RANGE = {
|
||||
-- @param #number boxlength Length of strafe pit box in meters.
|
||||
-- @param #number boxwidth Width of strafe pit box in meters.
|
||||
-- @param #number goodpass Number of hits for a good strafing pit pass.
|
||||
-- @param #number foulline Distance of foul line in meters.
|
||||
-- @param #number foulline Distance of foul line in meters.
|
||||
RANGE.Defaults = {
|
||||
goodhitrange = 25,
|
||||
strafemaxalt = 914,
|
||||
@@ -625,9 +625,9 @@ function RANGE:New( RangeName, Coalition )
|
||||
-- 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.Coalition = Coalition
|
||||
|
||||
|
||||
-- Log id.
|
||||
self.lid = string.format( "RANGE %s | ", self.rangename )
|
||||
|
||||
@@ -993,9 +993,9 @@ end
|
||||
-- @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
|
||||
|
||||
@@ -1200,7 +1200,7 @@ end
|
||||
-- @param #string PathToSRS Path to SRS directory.
|
||||
-- @param #number Port SRS port. Default 5002.
|
||||
-- @param #number Coalition Coalition side, e.g. `coalition.side.BLUE` or `coalition.side.RED`. Default `coalition.side.BLUE`.
|
||||
-- @param #number Frequency Frequency to use. Default is 256 MHz for range control and 305 MHz for instructor. If given, both control and instructor get this frequency.
|
||||
-- @param #number Frequency Frequency to use. Default is 256 MHz for range control and 305 MHz for instructor. If given, both control and instructor get this frequency.
|
||||
-- @param #number Modulation Modulation to use, defaults to radio.modulation.AM
|
||||
-- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0
|
||||
-- @param #string PathToGoogleKey Path to Google TTS credentials.
|
||||
@@ -1208,9 +1208,9 @@ end
|
||||
function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey)
|
||||
|
||||
if PathToSRS or MSRS.path then
|
||||
|
||||
|
||||
self.useSRS=true
|
||||
|
||||
|
||||
self.controlmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 256, Modulation or radio.modulation.AM)
|
||||
self.controlmsrs:SetPort(Port or MSRS.port)
|
||||
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE)
|
||||
@@ -1224,12 +1224,12 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume,
|
||||
self.instructmsrs:SetLabel("RANGEI")
|
||||
self.instructmsrs:SetVolume(Volume or 1.0)
|
||||
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
|
||||
|
||||
if PathToGoogleKey then
|
||||
|
||||
if PathToGoogleKey then
|
||||
self.controlmsrs:SetGoogle(PathToGoogleKey)
|
||||
self.instructmsrs:SetGoogle(PathToGoogleKey)
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
|
||||
end
|
||||
@@ -1739,9 +1739,9 @@ end
|
||||
-- @param Core.Event#EVENTDATA EventData
|
||||
function RANGE:OnEventBirth( EventData )
|
||||
self:F( { eventbirth = EventData } )
|
||||
|
||||
|
||||
if not EventData.IniPlayerName then return end
|
||||
|
||||
|
||||
local _unitName = EventData.IniUnitName
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
|
||||
|
||||
@@ -1762,7 +1762,7 @@ function RANGE:OnEventBirth( EventData )
|
||||
|
||||
-- Reset current strafe status.
|
||||
self.strafeStatus[_uid] = nil
|
||||
|
||||
|
||||
if self.Coalition then
|
||||
if EventData.IniCoalition == self.Coalition then
|
||||
self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName )
|
||||
@@ -1771,7 +1771,7 @@ function RANGE:OnEventBirth( EventData )
|
||||
-- Add Menu commands after a delay of 0.1 seconds.
|
||||
self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName )
|
||||
end
|
||||
|
||||
|
||||
-- By default, some bomb impact points and do not flare each hit on target.
|
||||
self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData
|
||||
self.PlayerSettings[_playername].smokebombimpact = self.defaultsmokebomb
|
||||
@@ -1905,21 +1905,21 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
|
||||
local _distance = nil
|
||||
local _closeCoord = nil --Core.Point#COORDINATE
|
||||
local _hitquality = "POOR"
|
||||
|
||||
|
||||
-- Get callsign.
|
||||
local _callsign = self:_myname( playerData.unitname )
|
||||
|
||||
|
||||
local _playername=playerData.playername
|
||||
|
||||
|
||||
local _unit=playerData.unit
|
||||
|
||||
|
||||
-- Coordinate of impact point.
|
||||
local impactcoord = weapon:GetImpactCoordinate()
|
||||
|
||||
|
||||
-- Check if impact happened in range zone.
|
||||
local insidezone = self.rangezone:IsCoordinateInZone( impactcoord )
|
||||
|
||||
|
||||
|
||||
-- Smoke impact point of bomb.
|
||||
if playerData.smokebombimpact and insidezone then
|
||||
if playerData.delaysmoke then
|
||||
@@ -1928,19 +1928,19 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
|
||||
impactcoord:Smoke( playerData.smokecolor )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- 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 )
|
||||
|
||||
|
||||
if targetcoord then
|
||||
|
||||
|
||||
-- Distance between bomb and target.
|
||||
local _temp = impactcoord:Get2DDistance( targetcoord )
|
||||
|
||||
|
||||
-- Find closest target to last known position of the bomb.
|
||||
if _distance == nil or _temp < _distance then
|
||||
_distance = _temp
|
||||
@@ -1957,21 +1957,21 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
|
||||
else
|
||||
_hitquality = "POOR"
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Count if bomb fell less than ~1 km away from the target.
|
||||
if _distance and _distance <= self.scorebombdistance then
|
||||
-- Init bomb player results.
|
||||
if not self.bombPlayerResults[_playername] then
|
||||
self.bombPlayerResults[_playername] = {}
|
||||
end
|
||||
|
||||
|
||||
-- Local results.
|
||||
local _results = self.bombPlayerResults[_playername]
|
||||
|
||||
|
||||
local result = {} -- #RANGE.BombResult
|
||||
result.command=SOCKET.DataType.BOMBRESULT
|
||||
result.name = _closetTarget.name or "unknown"
|
||||
@@ -1993,24 +1993,24 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
|
||||
result.attackVel = attackVel
|
||||
result.attackAlt = attackAlt
|
||||
result.date=os and os.date() or "n/a"
|
||||
|
||||
|
||||
-- Add to table.
|
||||
table.insert( _results, result )
|
||||
|
||||
|
||||
-- Call impact.
|
||||
self:Impact( result, playerData )
|
||||
|
||||
|
||||
elseif insidezone then
|
||||
|
||||
|
||||
-- Send message.
|
||||
-- DONE SRS message
|
||||
local _message = string.format( "%s, weapon impacted too far from nearest range target (>%.1f km). No score!", _callsign, self.scorebombdistance / 1000 )
|
||||
if self.useSRS then
|
||||
local ttstext = string.format( "%s, weapon impacted too far from nearest range target, mor than %.1f kilometer. No score!", _callsign, self.scorebombdistance / 1000 )
|
||||
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
|
||||
self.controlsrsQ:NewTransmission(ttstext,nil,self.controlmsrs,nil,2)
|
||||
end
|
||||
self:_DisplayMessageToGroup( _unit, _message, nil, false )
|
||||
|
||||
|
||||
if self.rangecontrol then
|
||||
-- weapon impacted too far from the nearest target! No Score!
|
||||
if self.useSRS then
|
||||
@@ -2019,11 +2019,11 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
|
||||
self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
else
|
||||
self:T( self.lid .. "Weapon impacted outside range zone." )
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun).
|
||||
@@ -2036,7 +2036,7 @@ function RANGE:OnEventShot( EventData )
|
||||
if EventData.Weapon == nil or EventData.IniDCSUnit == nil or EventData.IniPlayerName == nil then
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
-- Create weapon object.
|
||||
local weapon=WEAPON:New(EventData.weapon)
|
||||
|
||||
@@ -2048,7 +2048,7 @@ function RANGE:OnEventShot( EventData )
|
||||
|
||||
-- Get player unit and name.
|
||||
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
|
||||
|
||||
|
||||
-- Distance Player-to-Range. Set this to larger value than the threshold.
|
||||
local dPR = self.BombtrackThreshold * 2
|
||||
|
||||
@@ -2063,16 +2063,16 @@ function RANGE:OnEventShot( EventData )
|
||||
|
||||
-- Player data.
|
||||
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
|
||||
|
||||
|
||||
-- Attack parameters.
|
||||
local attackHdg=_unit:GetHeading()
|
||||
local attackAlt=_unit:GetHeight()
|
||||
attackAlt = UTILS.MetersToFeet(attackAlt)
|
||||
local attackVel=_unit:GetVelocityKNOTS()
|
||||
local attackVel=_unit:GetVelocityKNOTS()
|
||||
|
||||
-- Tracking info and init of last bomb position.
|
||||
self:T( self.lid .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, weapon:GetTypeName(), weapon:GetName()))
|
||||
|
||||
|
||||
-- Set callback function on impact.
|
||||
weapon:SetFuncImpact(RANGE._OnImpact, self, playerData, attackHdg, attackAlt, attackVel)
|
||||
|
||||
@@ -2144,33 +2144,33 @@ end
|
||||
function RANGE:onafterEnterRange( From, Event, To, player )
|
||||
|
||||
if self.instructor and self.rangecontrol then
|
||||
|
||||
|
||||
if self.useSRS then
|
||||
|
||||
|
||||
|
||||
|
||||
local text = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f MHz", self.rangecontrolfreq)
|
||||
local ttstext = string.format("You entered the bombing range. For hit assessment, contact the range controller at %.3f mega hertz.", self.rangecontrolfreq)
|
||||
|
||||
|
||||
local group = player.client:GetGroup()
|
||||
|
||||
|
||||
self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10)
|
||||
|
||||
|
||||
else
|
||||
|
||||
|
||||
-- Range control radio frequency split.
|
||||
local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." )
|
||||
|
||||
|
||||
-- Radio message that player entered the range
|
||||
|
||||
|
||||
-- You entered the bombing range. For hit assessment, contact the range controller at xy MHz
|
||||
self.instructor:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath )
|
||||
self.instructor:Number2Transmission( RF[1] )
|
||||
|
||||
|
||||
if tonumber( RF[2] ) > 0 then
|
||||
self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath )
|
||||
self.instructor:Number2Transmission( RF[2] )
|
||||
end
|
||||
|
||||
|
||||
self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath )
|
||||
end
|
||||
end
|
||||
@@ -2188,11 +2188,11 @@ function RANGE:onafterExitRange( From, Event, To, player )
|
||||
if self.instructor then
|
||||
-- You left the bombing range zone. Have a nice day!
|
||||
if self.useSRS then
|
||||
|
||||
|
||||
local text = "You left the bombing range zone. "
|
||||
|
||||
|
||||
local r=math.random(5)
|
||||
|
||||
|
||||
if r==1 then
|
||||
text=text.."Have a nice day!"
|
||||
elseif r==2 then
|
||||
@@ -2202,9 +2202,9 @@ function RANGE:onafterExitRange( From, Event, To, player )
|
||||
elseif r==4 then
|
||||
text=text.."See you in two weeks!"
|
||||
elseif r==5 then
|
||||
text=text.."!"
|
||||
text=text.."!"
|
||||
end
|
||||
|
||||
|
||||
self.instructsrsQ:NewTransmission(text, nil, self.instructmsrs, nil, 1, {player.client:GetGroup()}, text, 10)
|
||||
else
|
||||
self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath )
|
||||
@@ -2238,7 +2238,7 @@ function RANGE:onafterImpact( From, Event, To, result, player )
|
||||
text = text .. string.format( " %s hit.", result.quality )
|
||||
|
||||
if self.rangecontrol then
|
||||
|
||||
|
||||
if self.useSRS then
|
||||
local group = player.client:GetGroup()
|
||||
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10)
|
||||
@@ -2263,10 +2263,10 @@ function RANGE:onafterImpact( From, Event, To, result, player )
|
||||
|
||||
-- Unit.
|
||||
if player.unitname and not self.useSRS then
|
||||
|
||||
|
||||
-- Get unit.
|
||||
local unit = UNIT:FindByName( player.unitname )
|
||||
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( unit, text, nil, true )
|
||||
self:T( self.lid .. text )
|
||||
@@ -2276,7 +2276,7 @@ function RANGE:onafterImpact( From, Event, To, result, player )
|
||||
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)
|
||||
@@ -2545,7 +2545,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName )
|
||||
local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult )
|
||||
|
||||
-- Get player results.
|
||||
local _results = self.strafePlayerResults[_playername]
|
||||
local _results = self.strafePlayerResults[_playername]
|
||||
|
||||
-- Create message.
|
||||
if _results == nil then
|
||||
@@ -2851,7 +2851,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
|
||||
end
|
||||
end
|
||||
text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive )
|
||||
end
|
||||
end
|
||||
if self.rangecontrol then
|
||||
local alive = "N/A"
|
||||
if self.rangecontrolrelayname then
|
||||
@@ -3079,10 +3079,10 @@ function RANGE:_CheckInZone( _unitName )
|
||||
local unitheading = 0 -- RangeBoss
|
||||
|
||||
if _unit and _playername then
|
||||
|
||||
|
||||
-- Player data.
|
||||
local playerData=self.PlayerSettings[_playername] -- #RANGE.PlayerData
|
||||
|
||||
|
||||
--- Function to check if unit is in zone and facing in the right direction and is below the max alt.
|
||||
local function checkme( targetheading, _zone )
|
||||
local zone = _zone -- Core.Zone#ZONE
|
||||
@@ -3096,7 +3096,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
if towardspit then
|
||||
|
||||
local vec3 = _unit:GetVec3()
|
||||
local vec2 = { x = vec3.x, y = vec3.z } -- DCS#Vec2
|
||||
local vec2 = { x = vec3.x, y = vec3.z } -- DCS#Vec2
|
||||
local landheight = land.getHeight( vec2 )
|
||||
local unitalt = vec3.y - landheight
|
||||
|
||||
@@ -3143,7 +3143,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( _unit, _msg, nil, true )
|
||||
|
||||
|
||||
if self.rangecontrol then
|
||||
if self.useSRS then
|
||||
local group = _unit:GetGroup()
|
||||
@@ -3162,9 +3162,9 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Result.
|
||||
local _result = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
|
||||
|
||||
|
||||
local _sound = nil -- #RANGE.Soundfile
|
||||
|
||||
|
||||
-- Calculate accuracy of run. Number of hits wrt number of rounds fired.
|
||||
local shots = _result.ammo - _ammo
|
||||
local accur = 0
|
||||
@@ -3174,7 +3174,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
accur = 100
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Results text and sound message.
|
||||
local resulttext=""
|
||||
if _result.pastfoulline == true then --
|
||||
@@ -3211,7 +3211,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( _unit, _text )
|
||||
|
||||
|
||||
-- Strafe result.
|
||||
local result = {} -- #RANGE.StrafeResult
|
||||
result.command=SOCKET.DataType.STRAFERESULT
|
||||
@@ -3228,14 +3228,14 @@ function RANGE:_CheckInZone( _unitName )
|
||||
result.rangename = self.rangename
|
||||
result.airframe=playerData.airframe
|
||||
result.invalid = _result.pastfoulline
|
||||
|
||||
|
||||
-- Griger Results.
|
||||
self:StrafeResult(playerData, result)
|
||||
|
||||
|
||||
-- Save trap sheet.
|
||||
if playerData and playerData.targeton and self.targetsheet then
|
||||
self:_SaveTargetSheet( _playername, result )
|
||||
end
|
||||
end
|
||||
|
||||
-- Voice over.
|
||||
if self.rangecontrol then
|
||||
@@ -3300,7 +3300,7 @@ function RANGE:_CheckInZone( _unitName )
|
||||
|
||||
-- Send message.
|
||||
self:_DisplayMessageToGroup( _unit, _msg, 10, true )
|
||||
|
||||
|
||||
-- Trigger event that player is rolling in.
|
||||
self:RollingIn(playerData, target)
|
||||
|
||||
@@ -3436,18 +3436,18 @@ function RANGE:_GetBombTargetCoordinate( target )
|
||||
local coord = nil -- Core.Point#COORDINATE
|
||||
|
||||
if target.type == RANGE.TargetType.UNIT then
|
||||
|
||||
|
||||
-- Check if alive
|
||||
if target.target and target.target:IsAlive() then
|
||||
-- Get current position.
|
||||
coord = target.target:GetCoordinate()
|
||||
-- Save as last known position in case target dies.
|
||||
target.coordinate=coord
|
||||
target.coordinate=coord
|
||||
else
|
||||
-- Use stored position.
|
||||
coord = target.coordinate
|
||||
end
|
||||
|
||||
|
||||
elseif target.type == RANGE.TargetType.STATIC then
|
||||
|
||||
-- Static targets dont move.
|
||||
@@ -3457,11 +3457,11 @@ function RANGE:_GetBombTargetCoordinate( target )
|
||||
|
||||
-- Coordinates dont move.
|
||||
coord = target.coordinate
|
||||
|
||||
|
||||
elseif target.type == RANGE.TargetType.SCENERY then
|
||||
|
||||
-- Coordinates dont move.
|
||||
coord = target.coordinate
|
||||
coord = target.coordinate
|
||||
|
||||
else
|
||||
self:E( self.lid .. "ERROR: Unknown target type." )
|
||||
@@ -3668,7 +3668,7 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display, _to
|
||||
local playermessage = self.PlayerSettings[playername].messages
|
||||
|
||||
-- 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
|
||||
if _togroup and _grp then
|
||||
local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp)
|
||||
@@ -4023,9 +4023,9 @@ function RANGE:_GetPlayerUnitAndName( _unitName )
|
||||
self:F2( _unitName )
|
||||
|
||||
if _unitName ~= nil then
|
||||
|
||||
|
||||
local multiplayer = false
|
||||
|
||||
|
||||
-- Get DCS unit from its name.
|
||||
local DCSunit = Unit.getByName( _unitName )
|
||||
|
||||
@@ -4064,7 +4064,7 @@ function RANGE:_myname( unitname )
|
||||
if grp and grp:IsAlive() then
|
||||
pname = grp:GetCustomCallSign(true,true)
|
||||
end
|
||||
end
|
||||
end
|
||||
return pname
|
||||
end
|
||||
|
||||
|
||||
@@ -255,6 +255,7 @@
|
||||
-- @field #boolean skipperUturn U-turn on/off via menu.
|
||||
-- @field #number skipperOffset Holding offset angle in degrees for Case II/III manual recoveries.
|
||||
-- @field #number skipperTime Recovery time in min for manual recovery.
|
||||
-- @field #boolean intowindold If true, use old into wind calculation.
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
--- Be the boss!
|
||||
@@ -2724,6 +2725,18 @@ function AIRBOSS:SetLSOCallInterval( TimeInterval )
|
||||
return self
|
||||
end
|
||||
|
||||
--- Set if old into wind calculation is used when carrier turns into the wind for a recovery.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #boolean SwitchOn If `true` or `nil`, use old into wind calculation.
|
||||
-- @return #AIRBOSS self
|
||||
function AIRBOSS:SetIntoWindLegacy( SwitchOn )
|
||||
if SwitchOn==nil then
|
||||
SwitchOn=true
|
||||
end
|
||||
self.intowindold=SwitchOn
|
||||
return self
|
||||
end
|
||||
|
||||
--- Airboss is a rather nice guy and not strictly following the rules. Fore example, he does allow you into the landing pattern if you are not coming from the Marshal stack.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #boolean Switch If true or nil, Airboss bends the rules a bit.
|
||||
@@ -3641,6 +3654,12 @@ function AIRBOSS:onafterStatus( From, Event, To )
|
||||
local pos = self:GetCoordinate()
|
||||
local speed = self.carrier:GetVelocityKNOTS()
|
||||
|
||||
-- Update magnetic variation if we can get it from DCS.
|
||||
if require then
|
||||
self.magvar=pos:GetMagneticDeclination()
|
||||
--env.info(string.format("FF magvar=%.1f", self.magvar))
|
||||
end
|
||||
|
||||
-- Check water is ahead.
|
||||
local collision = false -- self:_CheckCollisionCoord(pos:Translate(self.collisiondist, hdg))
|
||||
|
||||
@@ -5200,6 +5219,7 @@ function AIRBOSS:_InitVoiceOvers()
|
||||
TOMCAT = { file = "PILOT-Tomcat", suffix = "ogg", loud = false, subtitle = "", duration = 0.66, subduration = 5 },
|
||||
HORNET = { file = "PILOT-Hornet", suffix = "ogg", loud = false, subtitle = "", duration = 0.56, subduration = 5 },
|
||||
VIKING = { file = "PILOT-Viking", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 },
|
||||
GREYHOUND = { file = "PILOT-Greyhound", suffix = "ogg", loud = false, subtitle = "", duration = 0.61, subduration = 5 },
|
||||
BALL = { file = "PILOT-Ball", suffix = "ogg", loud = false, subtitle = "", duration = 0.50, subduration = 5 },
|
||||
BINGOFUEL = { file = "PILOT-BingoFuel", suffix = "ogg", loud = false, subtitle = "", duration = 0.80 },
|
||||
GASATDIVERT = { file = "PILOT-GasAtDivert", suffix = "ogg", loud = false, subtitle = "", duration = 1.80 },
|
||||
@@ -6474,7 +6494,7 @@ function AIRBOSS:_LandAI( flight )
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.RHINOF
|
||||
or flight.actype == AIRBOSS.AircraftCarrier.GROWLER then
|
||||
Speed = UTILS.KnotsToKmph( 200 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D then
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.E2D or flight.actype == AIRBOSS.AircraftCarrier.C2A then
|
||||
Speed = UTILS.KnotsToKmph( 150 )
|
||||
elseif flight.actype == AIRBOSS.AircraftCarrier.F14A_AI or flight.actype == AIRBOSS.AircraftCarrier.F14A or flight.actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
Speed = UTILS.KnotsToKmph( 175 )
|
||||
@@ -11475,7 +11495,7 @@ end
|
||||
|
||||
--- Get wind direction and speed at carrier position.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number alt Altitude ASL in meters. Default 15 m.
|
||||
-- @param #number alt Altitude ASL in meters. Default 18 m.
|
||||
-- @param #boolean magnetic Direction including magnetic declination.
|
||||
-- @param Core.Point#COORDINATE coord (Optional) Coordinate at which to get the wind. Default is current carrier position.
|
||||
-- @return #number Direction the wind is blowing **from** in degrees.
|
||||
@@ -11547,10 +11567,31 @@ end
|
||||
|
||||
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number vdeck Desired wind velocity over deck in knots.
|
||||
-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned.
|
||||
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
|
||||
-- @return #number Carrier heading in degrees.
|
||||
function AIRBOSS:GetHeadingIntoWind_old( magnetic, coord )
|
||||
-- @return #number Carrier speed in knots to reach desired wind speed on deck.
|
||||
function AIRBOSS:GetHeadingIntoWind(vdeck, magnetic, coord )
|
||||
|
||||
if self.intowindold then
|
||||
--env.info("FF use OLD into wind")
|
||||
return self:GetHeadingIntoWind_old(vdeck, magnetic, coord)
|
||||
else
|
||||
--env.info("FF use NEW into wind")
|
||||
return self:GetHeadingIntoWind_new(vdeck, magnetic, coord)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
|
||||
-- @param #AIRBOSS self
|
||||
-- @param #number vdeck Desired wind velocity over deck in knots.
|
||||
-- @param #boolean magnetic If true, calculate magnetic heading. By default true heading is returned.
|
||||
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
|
||||
-- @return #number Carrier heading in degrees.
|
||||
function AIRBOSS:GetHeadingIntoWind_old( vdeck, magnetic, coord )
|
||||
|
||||
local function adjustDegreesForWindSpeed(windSpeed)
|
||||
local degreesAdjustment = 0
|
||||
@@ -11607,7 +11648,13 @@ function AIRBOSS:GetHeadingIntoWind_old( magnetic, coord )
|
||||
intowind = intowind + 360
|
||||
end
|
||||
|
||||
return intowind
|
||||
-- Wind speed.
|
||||
--local _, vwind = self:GetWind()
|
||||
|
||||
-- Speed of carrier in m/s but at least 4 knots.
|
||||
local vtot = math.max(vdeck-UTILS.MpsToKnots(vwind), 4)
|
||||
|
||||
return intowind, vtot
|
||||
end
|
||||
|
||||
--- Get true (or magnetic) heading of carrier into the wind. This accounts for the angled runway.
|
||||
@@ -11618,7 +11665,7 @@ end
|
||||
-- @param Core.Point#COORDINATE coord (Optional) Coordinate from which heading is calculated. Default is current carrier position.
|
||||
-- @return #number Carrier heading in degrees.
|
||||
-- @return #number Carrier speed in knots to reach desired wind speed on deck.
|
||||
function AIRBOSS:GetHeadingIntoWind( vdeck, magnetic, coord )
|
||||
function AIRBOSS:GetHeadingIntoWind_new( vdeck, magnetic, coord )
|
||||
|
||||
-- Default offset angle.
|
||||
local Offset=self.carrierparam.rwyangle or 0
|
||||
@@ -14279,6 +14326,8 @@ function AIRBOSS:_GetACNickname( actype )
|
||||
nickname = "Harrier"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.E2D then
|
||||
nickname = "Hawkeye"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.C2A then
|
||||
nickname = "Greyhound"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.F14A_AI or actype == AIRBOSS.AircraftCarrier.F14A or actype == AIRBOSS.AircraftCarrier.F14B then
|
||||
nickname = "Tomcat"
|
||||
elseif actype == AIRBOSS.AircraftCarrier.FA18C or actype == AIRBOSS.AircraftCarrier.HORNET then
|
||||
|
||||
@@ -292,10 +292,11 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2
|
||||
CSAR.AircraftType["Bronco-OV-10A"] = 2
|
||||
CSAR.AircraftType["MH-60R"] = 10
|
||||
CSAR.AircraftType["OH-6A"] = 2
|
||||
CSAR.AircraftType["OH-58D"] = 2
|
||||
|
||||
--- CSAR class version.
|
||||
-- @field #string version
|
||||
CSAR.version="1.0.22"
|
||||
CSAR.version="1.0.24"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- ToDo list
|
||||
@@ -735,7 +736,7 @@ function CSAR:_SpawnPilotInField(country,point,frequency,wetfeet)
|
||||
:NewWithAlias(template,alias)
|
||||
:InitCoalition(coalition)
|
||||
:InitCountry(country)
|
||||
:InitAIOnOff(pilotcacontrol)
|
||||
--:InitAIOnOff(pilotcacontrol)
|
||||
:InitDelayOff()
|
||||
:SpawnFromCoordinate(point)
|
||||
|
||||
@@ -1239,10 +1240,24 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage, _pla
|
||||
if not _nomessage then
|
||||
if _freq ~= 0 then --shagrat
|
||||
local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _groupName, _coordinatesText, _freqk)--shagrat _groupName to prevent 'f15_Pilot_Parachute'
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
|
||||
if self.coordtype ~= 2 then --not MGRS
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
|
||||
else
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
|
||||
local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
|
||||
local _text = string.format("%s requests SAR at %s, beacon at %.2f kilo hertz", _groupName, coordtext, _freqk)
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
|
||||
end
|
||||
else --shagrat CASEVAC msg
|
||||
local _text = string.format("Pickup Zone at %s.", _coordinatesText )
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
|
||||
if self.coordtype ~= 2 then --not MGRS
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime)
|
||||
else
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,false,true)
|
||||
local coordtext = UTILS.MGRSStringToSRSFriendly(_coordinatesText,true)
|
||||
local _text = string.format("Pickup Zone at %s.", coordtext )
|
||||
self:_DisplayToAllSAR(_text,self.coalition,self.messageTime,true,false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1946,23 +1961,28 @@ end
|
||||
--- (Internal) Display info to all SAR groups.
|
||||
-- @param #CSAR self
|
||||
-- @param #string _message Message to display.
|
||||
-- @param #number _side Coalition of message.
|
||||
-- @param #number _side Coalition of message.
|
||||
-- @param #number _messagetime How long to show.
|
||||
function CSAR:_DisplayToAllSAR(_message, _side, _messagetime)
|
||||
-- @param #boolean ToSRS If true or nil, send to SRS TTS
|
||||
-- @param #boolean ToScreen If true or nil, send to Screen
|
||||
function CSAR:_DisplayToAllSAR(_message, _side, _messagetime,ToSRS,ToScreen)
|
||||
self:T(self.lid .. " _DisplayToAllSAR")
|
||||
local messagetime = _messagetime or self.messageTime
|
||||
if self.msrs then
|
||||
self:T({_message,ToSRS=ToSRS,ToScreen=ToScreen})
|
||||
if self.msrs and (ToSRS == true or ToSRS == nil) then
|
||||
local voice = self.CSARVoice or MSRS.Voices.Google.Standard.en_GB_Standard_F
|
||||
if self.msrs:GetProvider() == MSRS.Provider.WINDOWS then
|
||||
voice = self.CSARVoiceMS or MSRS.Voices.Microsoft.Hedda
|
||||
end
|
||||
self:I("Voice = "..voice)
|
||||
self:F("Voice = "..voice)
|
||||
self.SRSQueue:NewTransmission(_message,duration,self.msrs,tstart,2,subgroups,subtitle,subduration,self.SRSchannel,self.SRSModulation,gender,culture,voice,volume,label,self.coordinate)
|
||||
end
|
||||
for _, _unitName in pairs(self.csarUnits) do
|
||||
local _unit = self:_GetSARHeli(_unitName)
|
||||
if _unit and not self.suppressmessages then
|
||||
self:_DisplayMessageToSAR(_unit, _message, _messagetime)
|
||||
if ToScreen == true or ToScreen == nil then
|
||||
for _, _unitName in pairs(self.csarUnits) do
|
||||
local _unit = self:_GetSARHeli(_unitName)
|
||||
if _unit and not self.suppressmessages then
|
||||
self:_DisplayMessageToSAR(_unit, _message, _messagetime)
|
||||
end
|
||||
end
|
||||
end
|
||||
return self
|
||||
|
||||
@@ -1250,11 +1250,12 @@ CTLD.UnitTypeCapabilities = {
|
||||
["AH-64D_BLK_II"] = {type="AH-64D_BLK_II", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 17, cargoweightlimit = 200}, -- 2 ppl **outside** the helo
|
||||
["Bronco-OV-10A"] = {type="Bronco-OV-10A", crates= false, troops=true, cratelimit = 0, trooplimit = 5, length = 13, cargoweightlimit = 1450},
|
||||
["OH-6A"] = {type="OH-6A", crates=false, troops=true, cratelimit = 0, trooplimit = 4, length = 7, cargoweightlimit = 550},
|
||||
["OH-58D"] = {type="OH-58D", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 14, cargoweightlimit = 400},
|
||||
}
|
||||
|
||||
--- CTLD class version.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.52"
|
||||
CTLD.version="1.0.54"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@@ -3608,7 +3609,7 @@ function CTLD:_MoveGroupToZone(Group)
|
||||
local groupcoord = Group:GetCoordinate()
|
||||
-- Get closest zone of type
|
||||
local outcome, name, zone, distance = self:IsUnitInZone(Group,CTLD.CargoZoneType.MOVE)
|
||||
if (distance <= self.movetroopsdistance) and zone then
|
||||
if (distance <= self.movetroopsdistance) and outcome == true and zone~= nil then
|
||||
-- yes, we can ;)
|
||||
local groupname = Group:GetName()
|
||||
local zonecoord = zone:GetRandomCoordinate(20,125) -- Core.Point#COORDINATE
|
||||
@@ -4465,10 +4466,9 @@ function CTLD:IsUnitInZone(Unit,Zonetype)
|
||||
zonewidth = zoneradius
|
||||
end
|
||||
local distance = self:_GetDistance(zonecoord,unitcoord)
|
||||
if zone:IsVec2InZone(unitVec2) and active then
|
||||
self:T("Distance Zone: "..distance)
|
||||
if (zone:IsVec2InZone(unitVec2) or Zonetype == CTLD.CargoZoneType.MOVE) and active == true and maxdist > distance then
|
||||
outcome = true
|
||||
end
|
||||
if maxdist > distance then
|
||||
maxdist = distance
|
||||
zoneret = zone
|
||||
zonenameret = zonename
|
||||
|
||||
@@ -55,6 +55,7 @@ BIGSMOKEPRESET = {
|
||||
-- @field #string MarianaIslands Mariana Islands map.
|
||||
-- @field #string Falklands South Atlantic map.
|
||||
-- @field #string Sinai Sinai map.
|
||||
-- @field #string Kola Kola map.
|
||||
DCSMAP = {
|
||||
Caucasus="Caucasus",
|
||||
NTTR="Nevada",
|
||||
@@ -64,7 +65,8 @@ DCSMAP = {
|
||||
Syria="Syria",
|
||||
MarianaIslands="MarianaIslands",
|
||||
Falklands="Falklands",
|
||||
Sinai="SinaiMap"
|
||||
Sinai="SinaiMap",
|
||||
Kola="Kola"
|
||||
}
|
||||
|
||||
|
||||
@@ -102,7 +104,7 @@ CALLSIGN={
|
||||
Shell=3,
|
||||
Navy_One=4,
|
||||
Mauler=5,
|
||||
Bloodhound=6,
|
||||
Bloodhound=6,
|
||||
},
|
||||
-- JTAC
|
||||
JTAC={
|
||||
@@ -416,7 +418,7 @@ function UTILS._OneLineSerialize(tbl)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
||||
tbl_str[#tbl_str + 1] = '}'
|
||||
return table.concat(tbl_str)
|
||||
else
|
||||
@@ -433,7 +435,7 @@ UTILS.BasicSerialize = function(s)
|
||||
if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'userdata') ) then
|
||||
return tostring(s)
|
||||
elseif type(s) == "table" then
|
||||
return UTILS._OneLineSerialize(s)
|
||||
return UTILS._OneLineSerialize(s)
|
||||
elseif type(s) == 'string' then
|
||||
s = string.format('(%s)', s)
|
||||
return s
|
||||
@@ -562,15 +564,15 @@ end
|
||||
-- @param #string fname File name.
|
||||
function UTILS.Gdump(fname)
|
||||
if lfs and io then
|
||||
|
||||
|
||||
local fdir = lfs.writedir() .. [[Logs\]] .. fname
|
||||
|
||||
|
||||
local f = io.open(fdir, 'w')
|
||||
|
||||
|
||||
f:write(UTILS.TableShow(_G))
|
||||
|
||||
|
||||
f:close()
|
||||
|
||||
|
||||
env.info(string.format('Wrote debug data to $1', fdir))
|
||||
else
|
||||
env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!")
|
||||
@@ -867,17 +869,17 @@ UTILS.tostringLLM2KData = function( lat, lon, acc)
|
||||
-- degrees, decimal minutes.
|
||||
latMin = UTILS.Round(latMin, acc)
|
||||
lonMin = UTILS.Round(lonMin, acc)
|
||||
|
||||
|
||||
if latMin == 60 then
|
||||
latMin = 0
|
||||
latDeg = latDeg + 1
|
||||
end
|
||||
|
||||
|
||||
if lonMin == 60 then
|
||||
lonMin = 0
|
||||
lonDeg = lonDeg + 1
|
||||
end
|
||||
|
||||
|
||||
local minFrmtStr -- create the formatting string for the minutes place
|
||||
if acc <= 0 then -- no decimal place.
|
||||
minFrmtStr = '%02d'
|
||||
@@ -885,7 +887,7 @@ UTILS.tostringLLM2KData = function( lat, lon, acc)
|
||||
local width = 3 + acc -- 01.310 - that's a width of 6, for example.
|
||||
minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
|
||||
end
|
||||
|
||||
|
||||
-- 024 23'N or 024 23.123'N
|
||||
return latHemi..string.format('%02d:', latDeg) .. string.format(minFrmtStr, latMin), lonHemi..string.format('%02d:', lonDeg) .. string.format(minFrmtStr, lonMin)
|
||||
|
||||
@@ -897,9 +899,9 @@ UTILS.tostringMGRS = function(MGRS, acc) --R2.1
|
||||
if acc <= 0 then
|
||||
return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph
|
||||
else
|
||||
|
||||
|
||||
if acc > 5 then acc = 5 end
|
||||
|
||||
|
||||
-- Test if Easting/Northing have less than 4 digits.
|
||||
--MGRS.Easting=123 -- should be 00123
|
||||
--MGRS.Northing=5432 -- should be 05432
|
||||
@@ -1382,7 +1384,7 @@ end
|
||||
function UTILS.VecDist2D(a, b)
|
||||
|
||||
local d = math.huge
|
||||
|
||||
|
||||
if (not a) or (not b) then return d end
|
||||
|
||||
local c={x=b.x-a.x, y=b.y-a.y}
|
||||
@@ -1398,12 +1400,12 @@ end
|
||||
-- @param DCS#Vec3 b Vector in 3D with x, y, z components.
|
||||
-- @return #number Distance between the vectors.
|
||||
function UTILS.VecDist3D(a, b)
|
||||
|
||||
|
||||
|
||||
|
||||
local d = math.huge
|
||||
|
||||
|
||||
if (not a) or (not b) then return d end
|
||||
|
||||
|
||||
local c={x=b.x-a.x, y=b.y-a.y, z=b.z-a.z}
|
||||
|
||||
d=math.sqrt(UTILS.VecDot(c, c))
|
||||
@@ -1703,6 +1705,7 @@ end
|
||||
-- * Mariana Islands +2 (East)
|
||||
-- * Falklands +12 (East) - note there's a LOT of deviation across the map, as we're closer to the South Pole
|
||||
-- * Sinai +4.8 (East)
|
||||
-- * Kola +15 (East) - not there is a lot of deviation across the map (-1° to +24°), as we are close to the North pole
|
||||
-- @param #string map (Optional) Map for which the declination is returned. Default is from env.mission.theatre
|
||||
-- @return #number Declination in degrees.
|
||||
function UTILS.GetMagneticDeclination(map)
|
||||
@@ -1729,6 +1732,8 @@ function UTILS.GetMagneticDeclination(map)
|
||||
declination=12
|
||||
elseif map==DCSMAP.Sinai then
|
||||
declination=4.8
|
||||
elseif map==DCSMAP.Kola then
|
||||
declination=15
|
||||
else
|
||||
declination=0
|
||||
end
|
||||
@@ -1796,7 +1801,7 @@ function UTILS.GetCoalitionEnemy(Coalition, Neutral)
|
||||
|
||||
local Coalitions={}
|
||||
if Coalition then
|
||||
if Coalition==coalition.side.RED then
|
||||
if Coalition==coalition.side.RED then
|
||||
Coalitions={coalition.side.BLUE}
|
||||
elseif Coalition==coalition.side.BLUE then
|
||||
Coalitions={coalition.side.RED}
|
||||
@@ -1804,7 +1809,7 @@ function UTILS.GetCoalitionEnemy(Coalition, Neutral)
|
||||
Coalitions={coalition.side.RED, coalition.side.BLUE}
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
if Neutral then
|
||||
table.insert(Coalitions, coalition.side.NEUTRAL)
|
||||
end
|
||||
@@ -1835,17 +1840,17 @@ end
|
||||
-- @param #number Typename The type name.
|
||||
-- @return #string The Reporting name or "Bogey".
|
||||
function UTILS.GetReportingName(Typename)
|
||||
|
||||
|
||||
local typename = string.lower(Typename)
|
||||
|
||||
|
||||
for name, value in pairs(ENUMS.ReportingName.NATO) do
|
||||
local svalue = string.lower(value)
|
||||
if string.find(typename,svalue,1,true) then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
return "Bogey"
|
||||
|
||||
return "Bogey"
|
||||
end
|
||||
|
||||
--- Get the callsign name from its enumerator value
|
||||
@@ -1876,49 +1881,49 @@ function UTILS.GetCallsignName(Callsign)
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.B1B) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.B52) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.F15E) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.F16) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.F18) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.FARP) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
for name, value in pairs(CALLSIGN.TransportAircraft) do
|
||||
if value==Callsign then
|
||||
return name
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
return "Ghostrider"
|
||||
end
|
||||
|
||||
@@ -1945,7 +1950,9 @@ function UTILS.GMTToLocalTimeDifference()
|
||||
elseif theatre==DCSMAP.Falklands then
|
||||
return -3 -- Fireland is UTC-3 hours.
|
||||
elseif theatre==DCSMAP.Sinai then
|
||||
return 2 -- Currently map is +2 but should be +3 (DCS bug?)
|
||||
return 2 -- Currently map is +2 but should be +3 (DCS bug?)
|
||||
elseif theatre==DCSMAP.Kola then
|
||||
return 3 -- Currently map is +2 but should be +3 (DCS bug?)
|
||||
else
|
||||
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
|
||||
return 0
|
||||
@@ -2150,19 +2157,19 @@ function UTILS.GetRandomTableElement(t, replace)
|
||||
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
|
||||
return
|
||||
end
|
||||
|
||||
|
||||
math.random()
|
||||
math.random()
|
||||
math.random()
|
||||
|
||||
|
||||
local r=math.random(#t)
|
||||
|
||||
|
||||
local element=t[r]
|
||||
|
||||
|
||||
if not replace then
|
||||
table.remove(t, r)
|
||||
end
|
||||
|
||||
|
||||
return element
|
||||
end
|
||||
|
||||
@@ -2191,7 +2198,7 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
BASE:T(unit_name .. " a side door is open ")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
if string.find(type_name, "SA342" ) and (unit:getDrawArgumentValue(34) == 1) then
|
||||
BASE:T(unit_name .. " front door(s) are open or doors removed")
|
||||
return true
|
||||
@@ -2216,7 +2223,7 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
BASE:T(unit_name .. " door is open")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
if type_name == "UH-60L" and (unit:getDrawArgumentValue(401) == 1 or unit:getDrawArgumentValue(402) == 1) then
|
||||
BASE:T(unit_name .. " cargo door is open")
|
||||
return true
|
||||
@@ -2226,22 +2233,27 @@ function UTILS.IsLoadingDoorOpen( unit_name )
|
||||
BASE:T(unit_name .. " front door(s) are open")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
if type_name == "AH-64D_BLK_II" then
|
||||
BASE:T(unit_name .. " front door(s) are open")
|
||||
return true -- no doors on this one ;)
|
||||
end
|
||||
|
||||
|
||||
if type_name == "Bronco-OV-10A" then
|
||||
BASE:T(unit_name .. " front door(s) are open")
|
||||
return true -- no doors on this one ;)
|
||||
end
|
||||
|
||||
|
||||
if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then
|
||||
BASE:T(unit_name .. " cargo door is open")
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
if type_name == " OH-58D" and (unit:getDrawArgumentValue(35) > 0 or unit:getDrawArgumentValue(421) == -1) then
|
||||
BASE:T(unit_name .. " cargo door is open")
|
||||
return true
|
||||
end
|
||||
|
||||
return false
|
||||
|
||||
end -- nil
|
||||
@@ -2350,7 +2362,7 @@ function UTILS.GenerateUHFrequencies(Start,End)
|
||||
|
||||
local FreeUHFFrequencies = {}
|
||||
local _start = 220000000
|
||||
|
||||
|
||||
if not Start then
|
||||
while _start < 399000000 do
|
||||
if _start ~= 243000000 then
|
||||
@@ -2361,7 +2373,7 @@ function UTILS.GenerateUHFrequencies(Start,End)
|
||||
else
|
||||
local myend = End*1000000 or 399000000
|
||||
local mystart = Start*1000000 or 220000000
|
||||
|
||||
|
||||
while _start < 399000000 do
|
||||
if _start ~= 243000000 and (_start < mystart or _start > myend) then
|
||||
print(_start)
|
||||
@@ -2369,10 +2381,10 @@ function UTILS.GenerateUHFrequencies(Start,End)
|
||||
end
|
||||
_start = _start + 500000
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
return FreeUHFFrequencies
|
||||
end
|
||||
|
||||
@@ -2413,7 +2425,7 @@ function UTILS.GenerateLaserCodes()
|
||||
return jtacGeneratedLaserCodes
|
||||
end
|
||||
|
||||
--- Ensure the passed object is a table.
|
||||
--- Ensure the passed object is a table.
|
||||
-- @param #table Object The object that should be a table.
|
||||
-- @param #boolean ReturnNil If `true`, return `#nil` if `Object` is nil. Otherwise an empty table `{}` is returned.
|
||||
-- @return #table The object that now certainly *is* a table.
|
||||
@@ -2425,11 +2437,11 @@ function UTILS.EnsureTable(Object, ReturnNil)
|
||||
end
|
||||
else
|
||||
if ReturnNil then
|
||||
return nil
|
||||
return nil
|
||||
else
|
||||
Object={}
|
||||
Object={}
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
return Object
|
||||
@@ -2441,30 +2453,30 @@ end
|
||||
-- @param #table Data The LUA data structure to save. This will be e.g. a table of text lines with an \\n at the end of each line.
|
||||
-- @return #boolean outcome True if saving is possible, else false.
|
||||
function UTILS.SaveToFile(Path,Filename,Data)
|
||||
-- Thanks to @FunkyFranky
|
||||
-- Thanks to @FunkyFranky
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
BASE:E("ERROR: io not desanitized. Can't save current file.")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- Check default path.
|
||||
if Path==nil and not lfs then
|
||||
BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
|
||||
-- Set path or default.
|
||||
local path = nil
|
||||
if lfs then
|
||||
path=Path or lfs.writedir()
|
||||
end
|
||||
|
||||
|
||||
-- Set file name.
|
||||
local filename=Filename
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
|
||||
-- write
|
||||
local f = assert(io.open(filename, "wb"))
|
||||
f:write(Data)
|
||||
@@ -2472,43 +2484,43 @@ function UTILS.SaveToFile(Path,Filename,Data)
|
||||
return true
|
||||
end
|
||||
|
||||
--- Function to save an object to a file
|
||||
--- Function to load an object from a file.
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @return #boolean outcome True if reading is possible and successful, else false.
|
||||
-- @return #table data The data read from the filesystem (table of lines of text). Each line is one single #string!
|
||||
function UTILS.LoadFromFile(Path,Filename)
|
||||
-- Thanks to @FunkyFranky
|
||||
-- Thanks to @FunkyFranky
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
BASE:E("ERROR: io not desanitized. Can't save current state.")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- Check default path.
|
||||
if Path==nil and not lfs then
|
||||
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
|
||||
-- Set path or default.
|
||||
local path = nil
|
||||
if lfs then
|
||||
path=Path or lfs.writedir()
|
||||
end
|
||||
|
||||
|
||||
-- Set file name.
|
||||
local filename=Filename
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
|
||||
-- Check if file exists.
|
||||
local exists=UTILS.CheckFileExists(Path,Filename)
|
||||
if not exists then
|
||||
BASE:I(string.format("ERROR: File %s does not exist!",filename))
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- read
|
||||
local file=assert(io.open(filename, "rb"))
|
||||
local loadeddata = {}
|
||||
@@ -2535,30 +2547,30 @@ function UTILS.CheckFileExists(Path,Filename)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Check io module is available.
|
||||
if not io then
|
||||
BASE:E("ERROR: io not desanitized.")
|
||||
return false
|
||||
end
|
||||
|
||||
|
||||
-- Check default path.
|
||||
if Path==nil and not lfs then
|
||||
BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
|
||||
end
|
||||
|
||||
|
||||
-- Set path or default.
|
||||
local path = nil
|
||||
if lfs then
|
||||
path=Path or lfs.writedir()
|
||||
end
|
||||
|
||||
|
||||
-- Set file name.
|
||||
local filename=Filename
|
||||
if path~=nil then
|
||||
filename=path.."\\"..filename
|
||||
end
|
||||
|
||||
|
||||
-- Check if file exists.
|
||||
local exists=_fileexists(filename)
|
||||
if not exists then
|
||||
@@ -2595,7 +2607,7 @@ end
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
-- @usage
|
||||
-- We will go through the list and find the corresponding group and save the current group size (0 when dead).
|
||||
-- These groups are supposed to be put on the map in the ME and have *not* moved (e.g. stationary SAM sites).
|
||||
-- These groups are supposed to be put on the map in the ME and have *not* moved (e.g. stationary SAM sites).
|
||||
-- Position is still saved for your usage.
|
||||
-- The idea is to reduce the number of units when reloading the data again to restart the saved mission.
|
||||
-- The data will be a simple comma separated list of groupname and size, with one header line.
|
||||
@@ -2634,12 +2646,12 @@ end
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
-- @usage
|
||||
-- We will go through the set and find the corresponding group and save the current group size and current position.
|
||||
-- The idea is to respawn the groups **spawned during an earlier run of the mission** at the given location and reduce
|
||||
-- the number of units in the group when reloading the data again to restart the saved mission. Note that *dead* groups
|
||||
-- The idea is to respawn the groups **spawned during an earlier run of the mission** at the given location and reduce
|
||||
-- the number of units in the group when reloading the data again to restart the saved mission. Note that *dead* groups
|
||||
-- cannot be covered with this.
|
||||
-- **Note** Do NOT use dashes or hashes in group template names (-,#)!
|
||||
-- The data will be a simple comma separated list of groupname and size, with one header line.
|
||||
-- The current task/waypoint/etc cannot be restored.
|
||||
-- The current task/waypoint/etc cannot be restored.
|
||||
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local data = "--Save SET of groups: "..Filename .."\n"
|
||||
@@ -2649,9 +2661,12 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
|
||||
if group and group:IsAlive() then
|
||||
local name = group:GetName()
|
||||
local template = string.gsub(name,"-(.+)$","")
|
||||
if string.find(name,"AID") then
|
||||
template = string.gsub(name,"(.AID.%d+$","")
|
||||
end
|
||||
if string.find(template,"#") then
|
||||
template = string.gsub(name,"#(%d+)$","")
|
||||
end
|
||||
end
|
||||
local units = group:CountAliveUnits()
|
||||
local position = group:GetVec3()
|
||||
if Structured then
|
||||
@@ -2663,7 +2678,7 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
|
||||
else
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- save the data
|
||||
@@ -2734,16 +2749,16 @@ end
|
||||
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
|
||||
|
||||
|
||||
local fires = {}
|
||||
|
||||
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
|
||||
local function Cruncher(group,typename,anzahl)
|
||||
local units = group:GetUnits()
|
||||
local reduced = 0
|
||||
@@ -2761,7 +2776,7 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local reduce = true
|
||||
if Reduce == false then reduce = false end
|
||||
local filename = Filename or "StateListofGroups"
|
||||
@@ -2803,13 +2818,13 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
|
||||
end
|
||||
local reduce = false
|
||||
if loadednumber < _number then reduce = true end
|
||||
|
||||
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
|
||||
|
||||
|
||||
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
|
||||
|
||||
if reduce then
|
||||
Cruncher(actualgroup,_name,_number-loadednumber)
|
||||
Cruncher(actualgroup,_name,_number-loadednumber)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
else
|
||||
local reduction = actualgroup:CountAliveUnits() - size
|
||||
@@ -2824,7 +2839,7 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
|
||||
end
|
||||
end
|
||||
table.insert(datatable,data)
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -2839,11 +2854,11 @@ end
|
||||
-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return Core.Set#SET_GROUP Set of GROUP objects.
|
||||
-- @return Core.Set#SET_GROUP Set of GROUP objects.
|
||||
-- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }`
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
|
||||
|
||||
|
||||
local fires = {}
|
||||
local usedtemplates = {}
|
||||
local spawn = true
|
||||
@@ -2851,14 +2866,14 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local setdata = SET_GROUP:New()
|
||||
local datatable = {}
|
||||
|
||||
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
|
||||
local function Cruncher(group,typename,anzahl)
|
||||
local units = group:GetUnits()
|
||||
local reduced = 0
|
||||
@@ -2876,7 +2891,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function PostSpawn(args)
|
||||
local spwndgrp = args[1]
|
||||
local size = args[2]
|
||||
@@ -2886,16 +2901,16 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
local actualsize = spwndgrp:CountAliveUnits()
|
||||
if actualsize > size then
|
||||
if Structured and structure then
|
||||
|
||||
|
||||
local loadedstructure = {}
|
||||
local strcset = UTILS.Split(structure,";")
|
||||
for _,_data in pairs(strcset) do
|
||||
local datasplit = UTILS.Split(_data,"==")
|
||||
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
|
||||
end
|
||||
|
||||
|
||||
local originalstructure = UTILS.GetCountPerTypeName(spwndgrp)
|
||||
|
||||
|
||||
for _name,_number in pairs(originalstructure) do
|
||||
local loadednumber = 0
|
||||
if loadedstructure[_name] then
|
||||
@@ -2903,11 +2918,11 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
end
|
||||
local reduce = false
|
||||
if loadednumber < _number then reduce = true end
|
||||
|
||||
|
||||
if reduce then
|
||||
Cruncher(spwndgrp,_name,_number-loadednumber)
|
||||
Cruncher(spwndgrp,_name,_number-loadednumber)
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
else
|
||||
local reduction = actualsize-size
|
||||
@@ -2920,16 +2935,16 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function MultiUse(Data)
|
||||
local template = Data.template
|
||||
local template = Data.template
|
||||
if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then
|
||||
-- multispawn
|
||||
if not usedtemplates[template].done then
|
||||
local spwnd = 0
|
||||
local spawngrp = SPAWN:New(template)
|
||||
spawngrp:InitLimit(0,usedtemplates[template].used)
|
||||
for _,_entry in pairs(usedtemplates[template].data) do
|
||||
for _,_entry in pairs(usedtemplates[template].data) do
|
||||
spwnd = spwnd + 1
|
||||
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
|
||||
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
|
||||
@@ -2941,7 +2956,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--BASE:I("Spawn = "..tostring(spawn))
|
||||
if UTILS.CheckFileExists(Path,filename) then
|
||||
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
|
||||
@@ -2975,13 +2990,13 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
|
||||
end
|
||||
end
|
||||
end
|
||||
for _id,_entry in pairs (datatable) do
|
||||
for _id,_entry in pairs (datatable) do
|
||||
if spawn and not MultiUse(_entry) and _entry.size > 0 then
|
||||
local group = SPAWN:New(_entry.template)
|
||||
local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
|
||||
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -3010,7 +3025,7 @@ function UTILS.LoadSetOfStatics(Path,Filename)
|
||||
if StaticObject then
|
||||
datatable:AddObject(StaticObject)
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -3026,7 +3041,7 @@ end
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. Dead objects will have coordinate points `{x=0,y=0,z=0}`
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
-- Returns nil when file cannot be read.
|
||||
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
|
||||
local fires = {}
|
||||
@@ -3062,7 +3077,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,E
|
||||
if Cinematic then
|
||||
local effect = math.random(8)
|
||||
if type(Effect) == "number" then
|
||||
effect = Effect
|
||||
effect = Effect
|
||||
end
|
||||
coord:BigSmokeAndFire(effect,Density,staticname)
|
||||
table.insert(fires,staticname)
|
||||
@@ -3072,7 +3087,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,E
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
@@ -3120,10 +3135,10 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
|
||||
if aspect == "" then
|
||||
BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track)
|
||||
else
|
||||
BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track)
|
||||
BRAANATO = string.format("%s, BRAA, %03d, %d miles, Angels %d, %s, Track %s",GroupWords, bearing, rangeNM, alt, aspect, track)
|
||||
end
|
||||
end
|
||||
return BRAANATO
|
||||
return BRAANATO
|
||||
end
|
||||
|
||||
--- Check if an object is contained in a table.
|
||||
@@ -3168,7 +3183,7 @@ function UTILS.IsAnyInTable(Table, Objects, Key)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
return false
|
||||
@@ -3184,30 +3199,30 @@ end
|
||||
-- @param #table Color Color of the line in RGB, e.g. {1,0,0} for red
|
||||
-- @param #number Alpha Transparency factor, between 0.1 and 1
|
||||
-- @param #number LineType Line type to be used, line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid.
|
||||
-- @param #boolean ReadOnly
|
||||
-- @param #boolean ReadOnly
|
||||
function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalition, Color, Alpha, LineType, ReadOnly)
|
||||
local fix_coordinate = Coordinate
|
||||
local altitude = Altitude
|
||||
local speed = Speed or 350
|
||||
local heading = Heading or 270
|
||||
local leg_distance = Leg or 10
|
||||
|
||||
|
||||
local coalition = Coalition or -1
|
||||
local color = Color or {1,0,0}
|
||||
local alpha = Alpha or 1
|
||||
local lineType = LineType or 1
|
||||
|
||||
|
||||
|
||||
|
||||
speed = UTILS.IasToTas(speed, UTILS.FeetToMeters(altitude), oatcorr)
|
||||
|
||||
|
||||
local turn_radius = 0.0211 * speed -3.01
|
||||
|
||||
|
||||
local point_two = fix_coordinate:Translate(UTILS.NMToMeters(leg_distance), heading, true, false)
|
||||
local point_three = point_two:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false)
|
||||
local point_four = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius)*2, heading - 90, true, false)
|
||||
local circle_center_fix_four = point_two:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false)
|
||||
local circle_center_two_three = fix_coordinate:Translate(UTILS.NMToMeters(turn_radius), heading - 90, true, false)
|
||||
|
||||
|
||||
|
||||
fix_coordinate:LineToAll(point_two, coalition, color, alpha, lineType)
|
||||
point_four:LineToAll(point_three, coalition, color, alpha, lineType)
|
||||
@@ -3881,3 +3896,46 @@ function UTILS.ClockHeadingString(refHdg,tgtHdg)
|
||||
local clockPos = math.ceil((relativeAngle % 360) / 30)
|
||||
return clockPos.." o'clock"
|
||||
end
|
||||
|
||||
--- Get a NATO abbreviated MGRS text for SRS use, optionally with prosody slow tag
|
||||
-- @param #string Text The input string, e.g. "MGRS 4Q FJ 12345 67890"
|
||||
-- @param #boolean Slow Optional - add slow tags
|
||||
-- @return #string Output for (Slow) spelling in SRS TTS e.g. "MGRS;<prosody rate="slow">4;Quebec;Foxtrot;Juliett;1;2;3;4;5;6;7;8;niner;zero;</prosody>"
|
||||
function UTILS.MGRSStringToSRSFriendly(Text,Slow)
|
||||
local Text = string.gsub(Text,"MGRS ","")
|
||||
Text = string.gsub(Text,"%s+","")
|
||||
Text = string.gsub(Text,"([%a%d])","%1;") -- "0;5;1;"
|
||||
Text = string.gsub(Text,"A","Alpha")
|
||||
Text = string.gsub(Text,"B","Bravo")
|
||||
Text = string.gsub(Text,"C","Charlie")
|
||||
Text = string.gsub(Text,"D","Delta")
|
||||
Text = string.gsub(Text,"E","Echo")
|
||||
Text = string.gsub(Text,"F","Foxtrot")
|
||||
Text = string.gsub(Text,"G","Golf")
|
||||
Text = string.gsub(Text,"H","Hotel")
|
||||
Text = string.gsub(Text,"I","India")
|
||||
Text = string.gsub(Text,"J","Juliett")
|
||||
Text = string.gsub(Text,"K","Kilo")
|
||||
Text = string.gsub(Text,"L","Lima")
|
||||
Text = string.gsub(Text,"M","Mike")
|
||||
Text = string.gsub(Text,"N","November")
|
||||
Text = string.gsub(Text,"O","Oscar")
|
||||
Text = string.gsub(Text,"P","Papa")
|
||||
Text = string.gsub(Text,"Q","Quebec")
|
||||
Text = string.gsub(Text,"R","Romeo")
|
||||
Text = string.gsub(Text,"S","Sierra")
|
||||
Text = string.gsub(Text,"T","Tango")
|
||||
Text = string.gsub(Text,"U","Uniform")
|
||||
Text = string.gsub(Text,"V","Victor")
|
||||
Text = string.gsub(Text,"W","Whiskey")
|
||||
Text = string.gsub(Text,"X","Xray")
|
||||
Text = string.gsub(Text,"Y","Yankee")
|
||||
Text = string.gsub(Text,"Z","Zulu")
|
||||
Text = string.gsub(Text,"0","zero")
|
||||
Text = string.gsub(Text,"9","niner")
|
||||
if Slow then
|
||||
Text = '<prosody rate="slow">'..Text..'</prosody>'
|
||||
end
|
||||
Text = "MGRS;"..Text
|
||||
return Text
|
||||
end
|
||||
|
||||
@@ -722,35 +722,35 @@ AIRBASE.Sinai = {
|
||||
|
||||
--- Airbases of the Kola map
|
||||
--
|
||||
-- * AIRBASE.Kola.Banak
|
||||
-- * AIRBASE.Kola.Bas_100
|
||||
-- * AIRBASE.Kola.Bodo
|
||||
-- * AIRBASE.Kola.Jokkmokk
|
||||
-- * AIRBASE.Kola.Kalixfors
|
||||
-- * AIRBASE.Kola.Kemi_Tornio
|
||||
-- * AIRBASE.Kola.Kiruna
|
||||
-- * AIRBASE.Kola.Lakselv
|
||||
-- * AIRBASE.Kola.Monchegorsk
|
||||
-- * AIRBASE.Kola.Murmansk_International
|
||||
-- * AIRBASE.Kola.Olenegorsk
|
||||
-- * AIRBASE.Kola.Olenya
|
||||
-- * AIRBASE.Kola.Rovaniemi
|
||||
-- * AIRBASE.Kola.Severomorsk1
|
||||
-- * AIRBASE.Kola.Severomorsk3
|
||||
-- * AIRBASE.Kola.Severomorsk_1
|
||||
-- * AIRBASE.Kola.Severomorsk_3
|
||||
--
|
||||
-- @field Kola
|
||||
AIRBASE.Kola = {
|
||||
["Banak"] = "Banak",
|
||||
["Bas_100"] = "Bas 100",
|
||||
["Bodo"] = "Bodo",
|
||||
["Jokkmokk"] = "Jokkmokk",
|
||||
["Kalixfors"] = "Kalixfors",
|
||||
["Kemi_Tornio"] = "Kemi Tornio",
|
||||
["Kiruna"] = "Kiruna",
|
||||
["Lakselv"] = "Lakselv",
|
||||
["Monchegorsk"] = "Monchegorsk",
|
||||
["Murmansk_International"] = "Murmansk International",
|
||||
["Olenegorsk"] = "Olenegorsk",
|
||||
["Olenya"] = "Olenya",
|
||||
["Rovaniemi"] = "Rovaniemi",
|
||||
["Severomorsk1"] = "Severomorsk1",
|
||||
["Severomorsk3"] = "Severomorsk3",
|
||||
["Severomorsk_1"] = "Severomorsk-1",
|
||||
["Severomorsk_3"] = "Severomorsk-3",
|
||||
}
|
||||
|
||||
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
|
||||
|
||||
@@ -1207,15 +1207,17 @@ function GROUP:GetCoordinate()
|
||||
-- no luck, try the API way
|
||||
|
||||
local DCSGroup = Group.getByName(self.GroupName)
|
||||
local DCSUnits = DCSGroup:getUnits() or {}
|
||||
for _,_unit in pairs(DCSUnits) do
|
||||
if Object.isExist(_unit) then
|
||||
local position = _unit:getPosition()
|
||||
local point = position.p ~= nil and position.p or _unit:GetPoint()
|
||||
if point then
|
||||
--self:I(point)
|
||||
local coord = COORDINATE:NewFromVec3(point)
|
||||
return coord
|
||||
if DCSGroup then
|
||||
local DCSUnits = DCSGroup:getUnits() or {}
|
||||
for _,_unit in pairs(DCSUnits) do
|
||||
if Object.isExist(_unit) then
|
||||
local position = _unit:getPosition()
|
||||
local point = position.p ~= nil and position.p or _unit:GetPoint()
|
||||
if point then
|
||||
--self:I(point)
|
||||
local coord = COORDINATE:NewFromVec3(point)
|
||||
return coord
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user