Merge branch 'master' into FF/MasterDevel

This commit is contained in:
Frank 2024-06-09 21:31:58 +02:00
commit 754430ba75
13 changed files with 539 additions and 466 deletions

View File

@ -1301,7 +1301,7 @@ function EVENT:onEvent( Event )
-- STATIC -- STATIC
--- ---
Event.TgtDCSUnit = Event.target 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() Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
-- Workaround for borked target info on cruise missiles -- Workaround for borked target info on cruise missiles
if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then if Event.TgtDCSUnitName and Event.TgtDCSUnitName ~= "" then

File diff suppressed because it is too large Load Diff

View File

@ -509,10 +509,10 @@ function MESSAGE.SetMSRS(PathToSRS,Port,PathToCredentials,Frequency,Modulation,G
end end
_MESSAGESRS.label = Label or MSRS.Label or "MESSAGE" _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.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.volume = Volume or MSRS.volume or 1
_MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume) _MESSAGESRS.MSRS:SetVolume(_MESSAGESRS.volume)

View File

@ -1516,6 +1516,7 @@ do
self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash )
self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnDeadOrCrash )
if self.Filter.Zones then if self.Filter.Zones then
self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self) self.ZoneTimer = TIMER:New(self._ContinousZoneFilter,self)
local timing = self.ZoneTimerInterval or 30 local timing = self.ZoneTimerInterval or 30

View File

@ -292,9 +292,10 @@ SPAWN = {
--- Enumerator for spawns at airbases --- Enumerator for spawns at airbases
-- @type SPAWN.Takeoff -- @type SPAWN.Takeoff
-- @extends Wrapper.Group#GROUP.Takeoff -- @field #number Air Take off happens in air.
-- @field #number Runway Spawn on runway. Does not work in MP!
-- @field #SPAWN.Takeoff Takeoff -- @field #number Hot Spawn at parking with engines on.
-- @field #number Cold Spawn at parking with engines off.
SPAWN.Takeoff = { SPAWN.Takeoff = {
Air = 1, Air = 1,
Runway = 2, Runway = 2,
@ -619,12 +620,14 @@ end
-- and any spaces before and after the resulting name are removed. -- and any spaces before and after the resulting name are removed.
-- IMPORTANT! This method MUST be the first used after :New !!! -- IMPORTANT! This method MUST be the first used after :New !!!
-- @param #SPAWN self -- @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 -- @return #SPAWN self
function SPAWN:InitKeepUnitNames( KeepUnitNames ) function SPAWN:InitKeepUnitNames( KeepUnitNames )
self:F() self:F()
self.SpawnInitKeepUnitNames = KeepUnitNames or true self.SpawnInitKeepUnitNames = false
if KeepUnitNames == true then self.SpawnInitKeepUnitNames = true end
return self return self
end end
@ -1609,8 +1612,8 @@ function SPAWN:SpawnWithIndex( SpawnIndex, NoBirth )
RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius ) RandomVec2 = PointVec3:GetRandomVec2InRadius( self.SpawnOuterRadius, self.SpawnInnerRadius )
numTries = numTries + 1 numTries = numTries + 1
inZone = SpawnZone:IsVec2InZone(RandomVec2) inZone = SpawnZone:IsVec2InZone(RandomVec2)
self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!") --self:I("Retrying " .. numTries .. "spawn " .. SpawnTemplate.name .. " in Zone " .. SpawnZone:GetName() .. "!")
self:I(SpawnZone) --self:I(SpawnZone)
end end
end end
if (not inZone) then if (not inZone) then
@ -3276,7 +3279,7 @@ end
--- Get the index from a given group. --- 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. -- The function will search the name of the group for a #, and will return the number behind the #-mark.
function SPAWN:GetSpawnIndexFromGroup( SpawnGroup ) 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 IndexString = string.match( SpawnGroup:GetName(), "#(%d*)$" ):sub( 2 )
local Index = tonumber( IndexString ) local Index = tonumber( IndexString )
@ -3288,7 +3291,7 @@ end
--- Return the last maximum index that can be used. --- Return the last maximum index that can be used.
function SPAWN:_GetLastIndex() function SPAWN:_GetLastIndex()
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } ) self:F3( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
return self.SpawnMaxGroups return self.SpawnMaxGroups
end end
@ -3436,24 +3439,28 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2
end end
if self.SpawnInitKeepUnitNames == false then if self.SpawnInitKeepUnitNames == false then
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
SpawnTemplate.units[UnitID].name = string.format( SpawnTemplate.name .. '-%02d', UnitID ) 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 SpawnTemplate.units[UnitID].unitId = nil
end end
else else
for UnitID = 1, #SpawnTemplate.units do for UnitID = 1, #SpawnTemplate.units do
local SpawnInitKeepUnitIFF = false local SpawnInitKeepUnitIFF = false
if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc if string.find(SpawnTemplate.units[UnitID].name,"#IFF_",1,true) then --Razbam IFF hack for F15E etc
SpawnInitKeepUnitIFF = true 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
end 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 SpawnTemplate.units[UnitID].unitId = nil
end end
end end

View File

@ -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 -- 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 -- world space points we can use
for _, point in UTILS.spairs(object["points"]) do 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"], local p = {x = object["mapX"] + point["x"],
y = object["mapY"] + point["y"] } 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 end
elseif object["polygonMode"] == "rect" then 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 -- 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} points = {p1, p2, p3, p4}
else else
-- bring the Arrow code over from Shape/Polygon
-- something else that might be added in the future -- something else that might be added in the future
end end
end end

View File

@ -22,7 +22,7 @@
-- @module Functional.Mantis -- @module Functional.Mantis
-- @image Functional.Mantis.jpg -- @image Functional.Mantis.jpg
-- --
-- Last Update: Feb 2024 -- Last Update: May 2024
------------------------------------------------------------------------- -------------------------------------------------------------------------
--- **MANTIS** class, extends Core.Base#BASE --- **MANTIS** class, extends Core.Base#BASE
@ -58,6 +58,7 @@
-- @field #boolean ShoradLink If true, #MANTIS has #SHORAD enabled -- @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 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 #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 -- @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 -- -- 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 -- -- 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. -- -- 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: -- ### 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 -- -- 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.3 SHORAD will automatically be added from SAM sites of type "short-range"
-- --
-- ### 2.1.4 Advanced features -- ### 2.1.4 Advanced features
-- --
-- -- switch off auto mode **before** you start MANTIS. -- -- switch off auto mode **before** you start MANTIS.
-- `mybluemantis.automode = false` -- mybluemantis.automode = false
-- --
-- -- switch off auto shorad **before** you start MANTIS. -- -- 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. -- -- scale of the activation range, i.e. don't activate at the fringes of max range, defaults below.
-- -- also see engagerange below. -- -- also see engagerange below.
-- ` self.radiusscale[MANTIS.SamType.LONG] = 1.1` -- self.radiusscale[MANTIS.SamType.LONG] = 1.1
-- ` self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2` -- self.radiusscale[MANTIS.SamType.MEDIUM] = 1.2
-- ` self.radiusscale[MANTIS.SamType.SHORT] = 1.3` -- 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] -- # 3. Default settings [both modes unless stated otherwise]
-- --
@ -321,6 +327,7 @@ MANTIS = {
automode = true, automode = true,
autoshorad = true, autoshorad = true,
ShoradGroupSet = nil, ShoradGroupSet = nil,
checkforfriendlies = false,
} }
--- Advanced state enumerator --- Advanced state enumerator
@ -502,7 +509,8 @@ do
-- DONE: Treat Awacs separately, since they might be >80km off site -- DONE: Treat Awacs separately, since they might be >80km off site
-- DONE: Allow tables of prefixes for the setup -- DONE: Allow tables of prefixes for the setup
-- DONE: Auto-Mode with range setups for various known SAM types. -- 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.SAM_Templates_Prefix = samprefix or "Red SAM"
self.EWR_Templates_Prefix = ewrprefix or "Red EWR" self.EWR_Templates_Prefix = ewrprefix or "Red EWR"
self.HQ_Template_CC = hq or nil self.HQ_Template_CC = hq or nil
@ -631,7 +639,7 @@ do
-- TODO Version -- TODO Version
-- @field #string version -- @field #string version
self.version="0.8.16" self.version="0.8.18"
self:I(string.format("***** Starting MANTIS Version %s *****", self.version)) self:I(string.format("***** Starting MANTIS Version %s *****", self.version))
--- FSM Functions --- --- FSM Functions ---
@ -1222,10 +1230,10 @@ do
function MANTIS:_PreFilterHeight(height) function MANTIS:_PreFilterHeight(height)
self:T(self.lid.."_PreFilterHeight") self:T(self.lid.."_PreFilterHeight")
local set = {} local set = {}
local dlink = self.Detection -- Ops.Intelligence#INTEL_DLINK local dlink = self.Detection -- Ops.Intel#INTEL_DLINK
local detectedgroups = dlink:GetContactTable() local detectedgroups = dlink:GetContactTable()
for _,_contact in pairs(detectedgroups) do 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 local grp = contact.group -- Wrapper.Group#GROUP
if grp:IsAlive() then if grp:IsAlive() then
if grp:GetHeight(true) < height then if grp:GetHeight(true) < height then
@ -1255,6 +1263,10 @@ do
-- DEBUG -- DEBUG
set = self:_PreFilterHeight(height) set = self:_PreFilterHeight(height)
end 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 for _,_coord in pairs (set) do
local coord = _coord -- get current coord to check local coord = _coord -- get current coord to check
-- output for cross-check -- output for cross-check
@ -1279,8 +1291,16 @@ do
local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug) local m = MESSAGE:New(text,10,"Check"):ToAllIf(self.debug)
self:T(self.lid..text) self:T(self.lid..text)
end 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 -- end output to cross-check
if targetdistance <= rad and zonecheck then if targetdistance <= rad and zonecheck == true and nofriendlies == true then
return true, targetdistance return true, targetdistance
end end
end end
@ -1777,7 +1797,7 @@ do
-- @return #MANTIS self -- @return #MANTIS self
function MANTIS:_CheckDLinkState() function MANTIS:_CheckDLinkState()
self:T(self.lid .. "_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() local TS = timer.getAbsTime()
if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then if not dlink:Is("Running") and (TS - self.DLTimeStamp > 29) then
self.DLink = false self.DLink = false

View File

@ -46,7 +46,7 @@
-- --
-- ### Contributions: FlightControl, Ciribob -- ### Contributions: FlightControl, Ciribob
-- ### SRS Additions: Applevangelist -- ### SRS Additions: Applevangelist
-- --
-- === -- ===
-- @module Functional.Range -- @module Functional.Range
-- @image Range.JPG -- @image Range.JPG
@ -102,7 +102,7 @@
-- @field #string targetpath Path where to save the target sheets. -- @field #string targetpath Path where to save the target sheets.
-- @field #string targetprefix File prefix for target sheet files. -- @field #string targetprefix File prefix for target sheet files.
-- @field Sound.SRS#MSRS controlmsrs SRS wrapper for range controller. -- @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#MSRS instructmsrs SRS wrapper for range instructor.
-- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor. -- @field Sound.SRS#MSRSQUEUE instructsrsQ SRS queue for range instructor.
-- @field #number Coalition Coalition side for the menu, if any. -- @field #number Coalition Coalition side for the menu, if any.
@ -169,7 +169,7 @@
-- --
-- ## Specifying Coordinates -- ## 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. -- coordinate will still be a valid impact point. This can be done via the @{#RANGE.AddBombingTargetCoordinate}(*coord*, *name*, *goodhitrange*) function.
-- --
-- # Fine Tuning -- # 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. -- 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 -- ## 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}(). -- Range control and instructor frequencies and voices can then be set via @{#RANGE.SetSRSRangeControl}() and @{#RANGE.SetSRSRangeInstructor}().
-- --
-- # Persistence -- # Persistence
@ -243,11 +243,11 @@
-- The next time you start the mission, these results are also automatically loaded. -- The next time you start the mission, these results are also automatically loaded.
-- --
-- Strafing results are currently **not** saved. -- Strafing results are currently **not** saved.
-- --
-- # FSM Events -- # FSM Events
-- --
-- This class creates additional events that can be used by mission designers for custom reactions -- 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} -- * `EnterRange` when a player enters a range zone. See @{#RANGE.OnAfterEnterRange}
-- * `ExitRange` when a player leaves a range zone. See @{#RANGE.OnAfterExitRange} -- * `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} -- * `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 boxlength Length of strafe pit box in meters.
-- @param #number boxwidth Width 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 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 = { RANGE.Defaults = {
goodhitrange = 25, goodhitrange = 25,
strafemaxalt = 914, strafemaxalt = 914,
@ -625,9 +625,9 @@ function RANGE:New( RangeName, Coalition )
-- Get range name. -- Get range name.
-- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu. -- TODO: make sure that the range name is not given twice. This would lead to problems in the F10 radio menu.
self.rangename = RangeName or "Practice Range" self.rangename = RangeName or "Practice Range"
self.Coalition = Coalition self.Coalition = Coalition
-- Log id. -- Log id.
self.lid = string.format( "RANGE %s | ", self.rangename ) self.lid = string.format( "RANGE %s | ", self.rangename )
@ -993,9 +993,9 @@ end
-- @param #string Host Host. Default "127.0.0.1". -- @param #string Host Host. Default "127.0.0.1".
-- @return #RANGE self -- @return #RANGE self
function RANGE:SetFunkManOn(Port, Host) function RANGE:SetFunkManOn(Port, Host)
self.funkmanSocket=SOCKET:New(Port, Host) self.funkmanSocket=SOCKET:New(Port, Host)
return self return self
end end
@ -1200,7 +1200,7 @@ end
-- @param #string PathToSRS Path to SRS directory. -- @param #string PathToSRS Path to SRS directory.
-- @param #number Port SRS port. Default 5002. -- @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 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 Modulation Modulation to use, defaults to radio.modulation.AM
-- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0 -- @param #number Volume Volume, between 0.0 and 1.0. Defaults to 1.0
-- @param #string PathToGoogleKey Path to Google TTS credentials. -- @param #string PathToGoogleKey Path to Google TTS credentials.
@ -1208,9 +1208,9 @@ end
function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey) function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, PathToGoogleKey)
if PathToSRS or MSRS.path then if PathToSRS or MSRS.path then
self.useSRS=true self.useSRS=true
self.controlmsrs=MSRS:New(PathToSRS or MSRS.path, Frequency or 256, Modulation or radio.modulation.AM) 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:SetPort(Port or MSRS.port)
self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) 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:SetLabel("RANGEI")
self.instructmsrs:SetVolume(Volume or 1.0) self.instructmsrs:SetVolume(Volume or 1.0)
self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") self.instructsrsQ = MSRSQUEUE:New("INSTRUCT")
if PathToGoogleKey then if PathToGoogleKey then
self.controlmsrs:SetGoogle(PathToGoogleKey) self.controlmsrs:SetGoogle(PathToGoogleKey)
self.instructmsrs:SetGoogle(PathToGoogleKey) self.instructmsrs:SetGoogle(PathToGoogleKey)
end end
else else
self:E(self.lid..string.format("ERROR: No SRS path specified!")) self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end end
@ -1739,9 +1739,9 @@ end
-- @param Core.Event#EVENTDATA EventData -- @param Core.Event#EVENTDATA EventData
function RANGE:OnEventBirth( EventData ) function RANGE:OnEventBirth( EventData )
self:F( { eventbirth = EventData } ) self:F( { eventbirth = EventData } )
if not EventData.IniPlayerName then return end if not EventData.IniPlayerName then return end
local _unitName = EventData.IniUnitName local _unitName = EventData.IniUnitName
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
@ -1762,7 +1762,7 @@ function RANGE:OnEventBirth( EventData )
-- Reset current strafe status. -- Reset current strafe status.
self.strafeStatus[_uid] = nil self.strafeStatus[_uid] = nil
if self.Coalition then if self.Coalition then
if EventData.IniCoalition == self.Coalition then if EventData.IniCoalition == self.Coalition then
self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) 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. -- Add Menu commands after a delay of 0.1 seconds.
self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName ) self:ScheduleOnce( 0.1, self._AddF10Commands, self, _unitName )
end end
-- By default, some bomb impact points and do not flare each hit on target. -- By default, some bomb impact points and do not flare each hit on target.
self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData self.PlayerSettings[_playername] = {} -- #RANGE.PlayerData
self.PlayerSettings[_playername].smokebombimpact = self.defaultsmokebomb self.PlayerSettings[_playername].smokebombimpact = self.defaultsmokebomb
@ -1905,21 +1905,21 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
local _distance = nil local _distance = nil
local _closeCoord = nil --Core.Point#COORDINATE local _closeCoord = nil --Core.Point#COORDINATE
local _hitquality = "POOR" local _hitquality = "POOR"
-- Get callsign. -- Get callsign.
local _callsign = self:_myname( playerData.unitname ) local _callsign = self:_myname( playerData.unitname )
local _playername=playerData.playername local _playername=playerData.playername
local _unit=playerData.unit local _unit=playerData.unit
-- Coordinate of impact point. -- Coordinate of impact point.
local impactcoord = weapon:GetImpactCoordinate() local impactcoord = weapon:GetImpactCoordinate()
-- Check if impact happened in range zone. -- Check if impact happened in range zone.
local insidezone = self.rangezone:IsCoordinateInZone( impactcoord ) local insidezone = self.rangezone:IsCoordinateInZone( impactcoord )
-- Smoke impact point of bomb. -- Smoke impact point of bomb.
if playerData.smokebombimpact and insidezone then if playerData.smokebombimpact and insidezone then
if playerData.delaysmoke then if playerData.delaysmoke then
@ -1928,19 +1928,19 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
impactcoord:Smoke( playerData.smokecolor ) impactcoord:Smoke( playerData.smokecolor )
end end
end end
-- Loop over defined bombing targets. -- Loop over defined bombing targets.
for _, _bombtarget in pairs( self.bombingTargets ) do for _, _bombtarget in pairs( self.bombingTargets ) do
local bombtarget=_bombtarget --#RANGE.BombTarget local bombtarget=_bombtarget --#RANGE.BombTarget
-- Get target coordinate. -- Get target coordinate.
local targetcoord = self:_GetBombTargetCoordinate( _bombtarget ) local targetcoord = self:_GetBombTargetCoordinate( _bombtarget )
if targetcoord then if targetcoord then
-- Distance between bomb and target. -- Distance between bomb and target.
local _temp = impactcoord:Get2DDistance( targetcoord ) local _temp = impactcoord:Get2DDistance( targetcoord )
-- Find closest target to last known position of the bomb. -- Find closest target to last known position of the bomb.
if _distance == nil or _temp < _distance then if _distance == nil or _temp < _distance then
_distance = _temp _distance = _temp
@ -1957,21 +1957,21 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
else else
_hitquality = "POOR" _hitquality = "POOR"
end end
end end
end end
end end
-- Count if bomb fell less than ~1 km away from the target. -- Count if bomb fell less than ~1 km away from the target.
if _distance and _distance <= self.scorebombdistance then if _distance and _distance <= self.scorebombdistance then
-- Init bomb player results. -- Init bomb player results.
if not self.bombPlayerResults[_playername] then if not self.bombPlayerResults[_playername] then
self.bombPlayerResults[_playername] = {} self.bombPlayerResults[_playername] = {}
end end
-- Local results. -- Local results.
local _results = self.bombPlayerResults[_playername] local _results = self.bombPlayerResults[_playername]
local result = {} -- #RANGE.BombResult local result = {} -- #RANGE.BombResult
result.command=SOCKET.DataType.BOMBRESULT result.command=SOCKET.DataType.BOMBRESULT
result.name = _closetTarget.name or "unknown" result.name = _closetTarget.name or "unknown"
@ -1993,24 +1993,24 @@ function RANGE._OnImpact(weapon, self, playerData, attackHdg, attackAlt, attackV
result.attackVel = attackVel result.attackVel = attackVel
result.attackAlt = attackAlt result.attackAlt = attackAlt
result.date=os and os.date() or "n/a" result.date=os and os.date() or "n/a"
-- Add to table. -- Add to table.
table.insert( _results, result ) table.insert( _results, result )
-- Call impact. -- Call impact.
self:Impact( result, playerData ) self:Impact( result, playerData )
elseif insidezone then elseif insidezone then
-- Send message. -- Send message.
-- DONE SRS 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 ) 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 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 ) 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 end
self:_DisplayMessageToGroup( _unit, _message, nil, false ) self:_DisplayMessageToGroup( _unit, _message, nil, false )
if self.rangecontrol then if self.rangecontrol then
-- weapon impacted too far from the nearest target! No Score! -- weapon impacted too far from the nearest target! No Score!
if self.useSRS then 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 ) self.rangecontrol:NewTransmission( RANGE.Sound.RCWeaponImpactedTooFar.filename, RANGE.Sound.RCWeaponImpactedTooFar.duration, self.soundpath, nil, nil, _message, self.subduration )
end end
end end
else else
self:T( self.lid .. "Weapon impacted outside range zone." ) self:T( self.lid .. "Weapon impacted outside range zone." )
end end
end end
--- Range event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). --- 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 if EventData.Weapon == nil or EventData.IniDCSUnit == nil or EventData.IniPlayerName == nil then
return return
end end
-- Create weapon object. -- Create weapon object.
local weapon=WEAPON:New(EventData.weapon) local weapon=WEAPON:New(EventData.weapon)
@ -2048,7 +2048,7 @@ function RANGE:OnEventShot( EventData )
-- Get player unit and name. -- Get player unit and name.
local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) local _unit, _playername = self:_GetPlayerUnitAndName( _unitName )
-- Distance Player-to-Range. Set this to larger value than the threshold. -- Distance Player-to-Range. Set this to larger value than the threshold.
local dPR = self.BombtrackThreshold * 2 local dPR = self.BombtrackThreshold * 2
@ -2063,16 +2063,16 @@ function RANGE:OnEventShot( EventData )
-- Player data. -- Player data.
local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData local playerData = self.PlayerSettings[_playername] -- #RANGE.PlayerData
-- Attack parameters. -- Attack parameters.
local attackHdg=_unit:GetHeading() local attackHdg=_unit:GetHeading()
local attackAlt=_unit:GetHeight() local attackAlt=_unit:GetHeight()
attackAlt = UTILS.MetersToFeet(attackAlt) attackAlt = UTILS.MetersToFeet(attackAlt)
local attackVel=_unit:GetVelocityKNOTS() local attackVel=_unit:GetVelocityKNOTS()
-- Tracking info and init of last bomb position. -- 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())) self:T( self.lid .. string.format( "RANGE %s: Tracking %s - %s.", self.rangename, weapon:GetTypeName(), weapon:GetName()))
-- Set callback function on impact. -- Set callback function on impact.
weapon:SetFuncImpact(RANGE._OnImpact, self, playerData, attackHdg, attackAlt, attackVel) weapon:SetFuncImpact(RANGE._OnImpact, self, playerData, attackHdg, attackAlt, attackVel)
@ -2144,33 +2144,33 @@ end
function RANGE:onafterEnterRange( From, Event, To, player ) function RANGE:onafterEnterRange( From, Event, To, player )
if self.instructor and self.rangecontrol then if self.instructor and self.rangecontrol then
if self.useSRS 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 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 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() local group = player.client:GetGroup()
self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10) self.instructsrsQ:NewTransmission(ttstext, nil, self.instructmsrs, nil, 1, {group}, text, 10)
else else
-- Range control radio frequency split. -- Range control radio frequency split.
local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." ) local RF = UTILS.Split( string.format( "%.3f", self.rangecontrolfreq ), "." )
-- Radio message that player entered the range -- Radio message that player entered the range
-- You entered the bombing range. For hit assessment, contact the range controller at xy MHz -- 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:NewTransmission( RANGE.Sound.IREnterRange.filename, RANGE.Sound.IREnterRange.duration, self.soundpath )
self.instructor:Number2Transmission( RF[1] ) self.instructor:Number2Transmission( RF[1] )
if tonumber( RF[2] ) > 0 then if tonumber( RF[2] ) > 0 then
self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath ) self.instructor:NewTransmission( RANGE.Sound.IRDecimal.filename, RANGE.Sound.IRDecimal.duration, self.soundpath )
self.instructor:Number2Transmission( RF[2] ) self.instructor:Number2Transmission( RF[2] )
end end
self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath ) self.instructor:NewTransmission( RANGE.Sound.IRMegaHertz.filename, RANGE.Sound.IRMegaHertz.duration, self.soundpath )
end end
end end
@ -2188,11 +2188,11 @@ function RANGE:onafterExitRange( From, Event, To, player )
if self.instructor then if self.instructor then
-- You left the bombing range zone. Have a nice day! -- You left the bombing range zone. Have a nice day!
if self.useSRS then if self.useSRS then
local text = "You left the bombing range zone. " local text = "You left the bombing range zone. "
local r=math.random(5) local r=math.random(5)
if r==1 then if r==1 then
text=text.."Have a nice day!" text=text.."Have a nice day!"
elseif r==2 then elseif r==2 then
@ -2202,9 +2202,9 @@ function RANGE:onafterExitRange( From, Event, To, player )
elseif r==4 then elseif r==4 then
text=text.."See you in two weeks!" text=text.."See you in two weeks!"
elseif r==5 then elseif r==5 then
text=text.."!" text=text.."!"
end end
self.instructsrsQ:NewTransmission(text, nil, self.instructmsrs, nil, 1, {player.client:GetGroup()}, text, 10) self.instructsrsQ:NewTransmission(text, nil, self.instructmsrs, nil, 1, {player.client:GetGroup()}, text, 10)
else else
self.instructor:NewTransmission( RANGE.Sound.IRExitRange.filename, RANGE.Sound.IRExitRange.duration, self.soundpath ) 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 ) text = text .. string.format( " %s hit.", result.quality )
if self.rangecontrol then if self.rangecontrol then
if self.useSRS then if self.useSRS then
local group = player.client:GetGroup() local group = player.client:GetGroup()
self.controlsrsQ:NewTransmission(text,nil,self.controlmsrs,nil,1,{group},text,10) 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. -- Unit.
if player.unitname and not self.useSRS then if player.unitname and not self.useSRS then
-- Get unit. -- Get unit.
local unit = UNIT:FindByName( player.unitname ) local unit = UNIT:FindByName( player.unitname )
-- Send message. -- Send message.
self:_DisplayMessageToGroup( unit, text, nil, true ) self:_DisplayMessageToGroup( unit, text, nil, true )
self:T( self.lid .. text ) self:T( self.lid .. text )
@ -2276,7 +2276,7 @@ function RANGE:onafterImpact( From, Event, To, result, player )
if self.autosave then if self.autosave then
self:Save() self:Save()
end end
-- Send result to FunkMan, which creates fancy MatLab figures and sends them to Discord via a bot. -- Send result to FunkMan, which creates fancy MatLab figures and sends them to Discord via a bot.
if self.funkmanSocket then if self.funkmanSocket then
self.funkmanSocket:SendTable(result) 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 ) local _message = string.format( "My Top %d Strafe Pit Results:\n", self.ndisplayresult )
-- Get player results. -- Get player results.
local _results = self.strafePlayerResults[_playername] local _results = self.strafePlayerResults[_playername]
-- Create message. -- Create message.
if _results == nil then if _results == nil then
@ -2851,7 +2851,7 @@ function RANGE:_DisplayRangeInfo( _unitname )
end end
end end
text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive ) text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive )
end end
if self.rangecontrol then if self.rangecontrol then
local alive = "N/A" local alive = "N/A"
if self.rangecontrolrelayname then if self.rangecontrolrelayname then
@ -3079,10 +3079,10 @@ function RANGE:_CheckInZone( _unitName )
local unitheading = 0 -- RangeBoss local unitheading = 0 -- RangeBoss
if _unit and _playername then if _unit and _playername then
-- Player data. -- Player data.
local playerData=self.PlayerSettings[_playername] -- #RANGE.PlayerData 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. --- 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 function checkme( targetheading, _zone )
local zone = _zone -- Core.Zone#ZONE local zone = _zone -- Core.Zone#ZONE
@ -3096,7 +3096,7 @@ function RANGE:_CheckInZone( _unitName )
if towardspit then if towardspit then
local vec3 = _unit:GetVec3() 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 landheight = land.getHeight( vec2 )
local unitalt = vec3.y - landheight local unitalt = vec3.y - landheight
@ -3143,7 +3143,7 @@ function RANGE:_CheckInZone( _unitName )
-- Send message. -- Send message.
self:_DisplayMessageToGroup( _unit, _msg, nil, true ) self:_DisplayMessageToGroup( _unit, _msg, nil, true )
if self.rangecontrol then if self.rangecontrol then
if self.useSRS then if self.useSRS then
local group = _unit:GetGroup() local group = _unit:GetGroup()
@ -3162,9 +3162,9 @@ function RANGE:_CheckInZone( _unitName )
-- Result. -- Result.
local _result = self.strafeStatus[_unitID] --#RANGE.StrafeStatus local _result = self.strafeStatus[_unitID] --#RANGE.StrafeStatus
local _sound = nil -- #RANGE.Soundfile local _sound = nil -- #RANGE.Soundfile
-- Calculate accuracy of run. Number of hits wrt number of rounds fired. -- Calculate accuracy of run. Number of hits wrt number of rounds fired.
local shots = _result.ammo - _ammo local shots = _result.ammo - _ammo
local accur = 0 local accur = 0
@ -3174,7 +3174,7 @@ function RANGE:_CheckInZone( _unitName )
accur = 100 accur = 100
end end
end end
-- Results text and sound message. -- Results text and sound message.
local resulttext="" local resulttext=""
if _result.pastfoulline == true then -- if _result.pastfoulline == true then --
@ -3211,7 +3211,7 @@ function RANGE:_CheckInZone( _unitName )
-- Send message. -- Send message.
self:_DisplayMessageToGroup( _unit, _text ) self:_DisplayMessageToGroup( _unit, _text )
-- Strafe result. -- Strafe result.
local result = {} -- #RANGE.StrafeResult local result = {} -- #RANGE.StrafeResult
result.command=SOCKET.DataType.STRAFERESULT result.command=SOCKET.DataType.STRAFERESULT
@ -3228,14 +3228,14 @@ function RANGE:_CheckInZone( _unitName )
result.rangename = self.rangename result.rangename = self.rangename
result.airframe=playerData.airframe result.airframe=playerData.airframe
result.invalid = _result.pastfoulline result.invalid = _result.pastfoulline
-- Griger Results. -- Griger Results.
self:StrafeResult(playerData, result) self:StrafeResult(playerData, result)
-- Save trap sheet. -- Save trap sheet.
if playerData and playerData.targeton and self.targetsheet then if playerData and playerData.targeton and self.targetsheet then
self:_SaveTargetSheet( _playername, result ) self:_SaveTargetSheet( _playername, result )
end end
-- Voice over. -- Voice over.
if self.rangecontrol then if self.rangecontrol then
@ -3300,7 +3300,7 @@ function RANGE:_CheckInZone( _unitName )
-- Send message. -- Send message.
self:_DisplayMessageToGroup( _unit, _msg, 10, true ) self:_DisplayMessageToGroup( _unit, _msg, 10, true )
-- Trigger event that player is rolling in. -- Trigger event that player is rolling in.
self:RollingIn(playerData, target) self:RollingIn(playerData, target)
@ -3436,18 +3436,18 @@ function RANGE:_GetBombTargetCoordinate( target )
local coord = nil -- Core.Point#COORDINATE local coord = nil -- Core.Point#COORDINATE
if target.type == RANGE.TargetType.UNIT then if target.type == RANGE.TargetType.UNIT then
-- Check if alive -- Check if alive
if target.target and target.target:IsAlive() then if target.target and target.target:IsAlive() then
-- Get current position. -- Get current position.
coord = target.target:GetCoordinate() coord = target.target:GetCoordinate()
-- Save as last known position in case target dies. -- Save as last known position in case target dies.
target.coordinate=coord target.coordinate=coord
else else
-- Use stored position. -- Use stored position.
coord = target.coordinate coord = target.coordinate
end end
elseif target.type == RANGE.TargetType.STATIC then elseif target.type == RANGE.TargetType.STATIC then
-- Static targets dont move. -- Static targets dont move.
@ -3457,11 +3457,11 @@ function RANGE:_GetBombTargetCoordinate( target )
-- Coordinates dont move. -- Coordinates dont move.
coord = target.coordinate coord = target.coordinate
elseif target.type == RANGE.TargetType.SCENERY then elseif target.type == RANGE.TargetType.SCENERY then
-- Coordinates dont move. -- Coordinates dont move.
coord = target.coordinate coord = target.coordinate
else else
self:E( self.lid .. "ERROR: Unknown target type." ) 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 local playermessage = self.PlayerSettings[playername].messages
-- Send message to player if messages enabled and not only for the examiner. -- 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 _gid and (playermessage == true or display) and (not self.examinerexclusive) then
if _togroup and _grp then if _togroup and _grp then
local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp) local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp)
@ -4023,9 +4023,9 @@ function RANGE:_GetPlayerUnitAndName( _unitName )
self:F2( _unitName ) self:F2( _unitName )
if _unitName ~= nil then if _unitName ~= nil then
local multiplayer = false local multiplayer = false
-- Get DCS unit from its name. -- Get DCS unit from its name.
local DCSunit = Unit.getByName( _unitName ) local DCSunit = Unit.getByName( _unitName )
@ -4064,7 +4064,7 @@ function RANGE:_myname( unitname )
if grp and grp:IsAlive() then if grp and grp:IsAlive() then
pname = grp:GetCustomCallSign(true,true) pname = grp:GetCustomCallSign(true,true)
end end
end end
return pname return pname
end end

View File

@ -292,10 +292,11 @@ CSAR.AircraftType["AH-64D_BLK_II"] = 2
CSAR.AircraftType["Bronco-OV-10A"] = 2 CSAR.AircraftType["Bronco-OV-10A"] = 2
CSAR.AircraftType["MH-60R"] = 10 CSAR.AircraftType["MH-60R"] = 10
CSAR.AircraftType["OH-6A"] = 2 CSAR.AircraftType["OH-6A"] = 2
CSAR.AircraftType["OH-58D"] = 2
--- CSAR class version. --- CSAR class version.
-- @field #string version -- @field #string version
CSAR.version="1.0.23" CSAR.version="1.0.24"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list

View File

@ -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 ["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}, ["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-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. --- CTLD class version.
-- @field #string version -- @field #string version
CTLD.version="1.0.53" CTLD.version="1.0.54"
--- Instantiate a new CTLD. --- Instantiate a new CTLD.
-- @param #CTLD self -- @param #CTLD self

View File

@ -104,7 +104,7 @@ CALLSIGN={
Shell=3, Shell=3,
Navy_One=4, Navy_One=4,
Mauler=5, Mauler=5,
Bloodhound=6, Bloodhound=6,
}, },
-- JTAC -- JTAC
JTAC={ JTAC={
@ -418,7 +418,7 @@ function UTILS._OneLineSerialize(tbl)
end end
end end
tbl_str[#tbl_str + 1] = '}' tbl_str[#tbl_str + 1] = '}'
return table.concat(tbl_str) return table.concat(tbl_str)
else else
@ -435,7 +435,7 @@ UTILS.BasicSerialize = function(s)
if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'userdata') ) then if ((type(s) == 'number') or (type(s) == 'boolean') or (type(s) == 'function') or (type(s) == 'userdata') ) then
return tostring(s) return tostring(s)
elseif type(s) == "table" then elseif type(s) == "table" then
return UTILS._OneLineSerialize(s) return UTILS._OneLineSerialize(s)
elseif type(s) == 'string' then elseif type(s) == 'string' then
s = string.format('(%s)', s) s = string.format('(%s)', s)
return s return s
@ -564,15 +564,15 @@ end
-- @param #string fname File name. -- @param #string fname File name.
function UTILS.Gdump(fname) function UTILS.Gdump(fname)
if lfs and io then if lfs and io then
local fdir = lfs.writedir() .. [[Logs\]] .. fname local fdir = lfs.writedir() .. [[Logs\]] .. fname
local f = io.open(fdir, 'w') local f = io.open(fdir, 'w')
f:write(UTILS.TableShow(_G)) f:write(UTILS.TableShow(_G))
f:close() f:close()
env.info(string.format('Wrote debug data to $1', fdir)) env.info(string.format('Wrote debug data to $1', fdir))
else else
env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!") env.error("WARNING: lfs and/or io not de-sanitized - cannot dump _G!")
@ -869,17 +869,17 @@ UTILS.tostringLLM2KData = function( lat, lon, acc)
-- degrees, decimal minutes. -- degrees, decimal minutes.
latMin = UTILS.Round(latMin, acc) latMin = UTILS.Round(latMin, acc)
lonMin = UTILS.Round(lonMin, acc) lonMin = UTILS.Round(lonMin, acc)
if latMin == 60 then if latMin == 60 then
latMin = 0 latMin = 0
latDeg = latDeg + 1 latDeg = latDeg + 1
end end
if lonMin == 60 then if lonMin == 60 then
lonMin = 0 lonMin = 0
lonDeg = lonDeg + 1 lonDeg = lonDeg + 1
end end
local minFrmtStr -- create the formatting string for the minutes place local minFrmtStr -- create the formatting string for the minutes place
if acc <= 0 then -- no decimal place. if acc <= 0 then -- no decimal place.
minFrmtStr = '%02d' minFrmtStr = '%02d'
@ -887,7 +887,7 @@ UTILS.tostringLLM2KData = function( lat, lon, acc)
local width = 3 + acc -- 01.310 - that's a width of 6, for example. local width = 3 + acc -- 01.310 - that's a width of 6, for example.
minFrmtStr = '%0' .. width .. '.' .. acc .. 'f' minFrmtStr = '%0' .. width .. '.' .. acc .. 'f'
end end
-- 024 23'N or 024 23.123'N -- 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) return latHemi..string.format('%02d:', latDeg) .. string.format(minFrmtStr, latMin), lonHemi..string.format('%02d:', lonDeg) .. string.format(minFrmtStr, lonMin)
@ -899,9 +899,9 @@ UTILS.tostringMGRS = function(MGRS, acc) --R2.1
if acc <= 0 then if acc <= 0 then
return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph return MGRS.UTMZone .. ' ' .. MGRS.MGRSDigraph
else else
if acc > 5 then acc = 5 end if acc > 5 then acc = 5 end
-- Test if Easting/Northing have less than 4 digits. -- Test if Easting/Northing have less than 4 digits.
--MGRS.Easting=123 -- should be 00123 --MGRS.Easting=123 -- should be 00123
--MGRS.Northing=5432 -- should be 05432 --MGRS.Northing=5432 -- should be 05432
@ -1384,7 +1384,7 @@ end
function UTILS.VecDist2D(a, b) function UTILS.VecDist2D(a, b)
local d = math.huge local d = math.huge
if (not a) or (not b) then return d end if (not a) or (not b) then return d end
local c={x=b.x-a.x, y=b.y-a.y} local c={x=b.x-a.x, y=b.y-a.y}
@ -1400,12 +1400,12 @@ end
-- @param DCS#Vec3 b Vector in 3D with x, y, z components. -- @param DCS#Vec3 b Vector in 3D with x, y, z components.
-- @return #number Distance between the vectors. -- @return #number Distance between the vectors.
function UTILS.VecDist3D(a, b) function UTILS.VecDist3D(a, b)
local d = math.huge local d = math.huge
if (not a) or (not b) then return d end 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} local c={x=b.x-a.x, y=b.y-a.y, z=b.z-a.z}
d=math.sqrt(UTILS.VecDot(c, c)) d=math.sqrt(UTILS.VecDot(c, c))
@ -1801,7 +1801,7 @@ function UTILS.GetCoalitionEnemy(Coalition, Neutral)
local Coalitions={} local Coalitions={}
if Coalition then if Coalition then
if Coalition==coalition.side.RED then if Coalition==coalition.side.RED then
Coalitions={coalition.side.BLUE} Coalitions={coalition.side.BLUE}
elseif Coalition==coalition.side.BLUE then elseif Coalition==coalition.side.BLUE then
Coalitions={coalition.side.RED} Coalitions={coalition.side.RED}
@ -1809,7 +1809,7 @@ function UTILS.GetCoalitionEnemy(Coalition, Neutral)
Coalitions={coalition.side.RED, coalition.side.BLUE} Coalitions={coalition.side.RED, coalition.side.BLUE}
end end
end end
if Neutral then if Neutral then
table.insert(Coalitions, coalition.side.NEUTRAL) table.insert(Coalitions, coalition.side.NEUTRAL)
end end
@ -1840,17 +1840,17 @@ end
-- @param #number Typename The type name. -- @param #number Typename The type name.
-- @return #string The Reporting name or "Bogey". -- @return #string The Reporting name or "Bogey".
function UTILS.GetReportingName(Typename) function UTILS.GetReportingName(Typename)
local typename = string.lower(Typename) local typename = string.lower(Typename)
for name, value in pairs(ENUMS.ReportingName.NATO) do for name, value in pairs(ENUMS.ReportingName.NATO) do
local svalue = string.lower(value) local svalue = string.lower(value)
if string.find(typename,svalue,1,true) then if string.find(typename,svalue,1,true) then
return name return name
end end
end end
return "Bogey" return "Bogey"
end end
--- Get the callsign name from its enumerator value --- Get the callsign name from its enumerator value
@ -1881,49 +1881,49 @@ function UTILS.GetCallsignName(Callsign)
return name return name
end end
end end
for name, value in pairs(CALLSIGN.B1B) do for name, value in pairs(CALLSIGN.B1B) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
for name, value in pairs(CALLSIGN.B52) do for name, value in pairs(CALLSIGN.B52) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
for name, value in pairs(CALLSIGN.F15E) do for name, value in pairs(CALLSIGN.F15E) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
for name, value in pairs(CALLSIGN.F16) do for name, value in pairs(CALLSIGN.F16) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
for name, value in pairs(CALLSIGN.F18) do for name, value in pairs(CALLSIGN.F18) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
for name, value in pairs(CALLSIGN.FARP) do for name, value in pairs(CALLSIGN.FARP) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
for name, value in pairs(CALLSIGN.TransportAircraft) do for name, value in pairs(CALLSIGN.TransportAircraft) do
if value==Callsign then if value==Callsign then
return name return name
end end
end end
return "Ghostrider" return "Ghostrider"
end end
@ -1950,7 +1950,9 @@ function UTILS.GMTToLocalTimeDifference()
elseif theatre==DCSMAP.Falklands then elseif theatre==DCSMAP.Falklands then
return -3 -- Fireland is UTC-3 hours. return -3 -- Fireland is UTC-3 hours.
elseif theatre==DCSMAP.Sinai then 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 else
BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre)))
return 0 return 0
@ -2155,19 +2157,19 @@ function UTILS.GetRandomTableElement(t, replace)
BASE:I("Error in ShuffleTable: Missing or wrong type of Argument") BASE:I("Error in ShuffleTable: Missing or wrong type of Argument")
return return
end end
math.random() math.random()
math.random() math.random()
math.random() math.random()
local r=math.random(#t) local r=math.random(#t)
local element=t[r] local element=t[r]
if not replace then if not replace then
table.remove(t, r) table.remove(t, r)
end end
return element return element
end end
@ -2196,7 +2198,7 @@ function UTILS.IsLoadingDoorOpen( unit_name )
BASE:T(unit_name .. " a side door is open ") BASE:T(unit_name .. " a side door is open ")
return true return true
end end
if string.find(type_name, "SA342" ) and (unit:getDrawArgumentValue(34) == 1) then if string.find(type_name, "SA342" ) and (unit:getDrawArgumentValue(34) == 1) then
BASE:T(unit_name .. " front door(s) are open or doors removed") BASE:T(unit_name .. " front door(s) are open or doors removed")
return true return true
@ -2221,7 +2223,7 @@ function UTILS.IsLoadingDoorOpen( unit_name )
BASE:T(unit_name .. " door is open") BASE:T(unit_name .. " door is open")
return true return true
end end
if type_name == "UH-60L" and (unit:getDrawArgumentValue(401) == 1 or unit:getDrawArgumentValue(402) == 1) then if type_name == "UH-60L" and (unit:getDrawArgumentValue(401) == 1 or unit:getDrawArgumentValue(402) == 1) then
BASE:T(unit_name .. " cargo door is open") BASE:T(unit_name .. " cargo door is open")
return true return true
@ -2231,22 +2233,27 @@ function UTILS.IsLoadingDoorOpen( unit_name )
BASE:T(unit_name .. " front door(s) are open") BASE:T(unit_name .. " front door(s) are open")
return true return true
end end
if type_name == "AH-64D_BLK_II" then if type_name == "AH-64D_BLK_II" then
BASE:T(unit_name .. " front door(s) are open") BASE:T(unit_name .. " front door(s) are open")
return true -- no doors on this one ;) return true -- no doors on this one ;)
end end
if type_name == "Bronco-OV-10A" then if type_name == "Bronco-OV-10A" then
BASE:T(unit_name .. " front door(s) are open") BASE:T(unit_name .. " front door(s) are open")
return true -- no doors on this one ;) return true -- no doors on this one ;)
end end
if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then if type_name == "MH-60R" and (unit:getDrawArgumentValue(403) > 0 or unit:getDrawArgumentValue(403) == -1) then
BASE:T(unit_name .. " cargo door is open") BASE:T(unit_name .. " cargo door is open")
return true return true
end 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 return false
end -- nil end -- nil
@ -2355,7 +2362,7 @@ function UTILS.GenerateUHFrequencies(Start,End)
local FreeUHFFrequencies = {} local FreeUHFFrequencies = {}
local _start = 220000000 local _start = 220000000
if not Start then if not Start then
while _start < 399000000 do while _start < 399000000 do
if _start ~= 243000000 then if _start ~= 243000000 then
@ -2366,7 +2373,7 @@ function UTILS.GenerateUHFrequencies(Start,End)
else else
local myend = End*1000000 or 399000000 local myend = End*1000000 or 399000000
local mystart = Start*1000000 or 220000000 local mystart = Start*1000000 or 220000000
while _start < 399000000 do while _start < 399000000 do
if _start ~= 243000000 and (_start < mystart or _start > myend) then if _start ~= 243000000 and (_start < mystart or _start > myend) then
print(_start) print(_start)
@ -2374,10 +2381,10 @@ function UTILS.GenerateUHFrequencies(Start,End)
end end
_start = _start + 500000 _start = _start + 500000
end end
end end
return FreeUHFFrequencies return FreeUHFFrequencies
end end
@ -2418,7 +2425,7 @@ function UTILS.GenerateLaserCodes()
return jtacGeneratedLaserCodes return jtacGeneratedLaserCodes
end 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 #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. -- @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. -- @return #table The object that now certainly *is* a table.
@ -2430,11 +2437,11 @@ function UTILS.EnsureTable(Object, ReturnNil)
end end
else else
if ReturnNil then if ReturnNil then
return nil return nil
else else
Object={} Object={}
end end
end end
return Object return Object
@ -2446,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. -- @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. -- @return #boolean outcome True if saving is possible, else false.
function UTILS.SaveToFile(Path,Filename,Data) function UTILS.SaveToFile(Path,Filename,Data)
-- Thanks to @FunkyFranky -- Thanks to @FunkyFranky
-- Check io module is available. -- Check io module is available.
if not io then if not io then
BASE:E("ERROR: io not desanitized. Can't save current file.") BASE:E("ERROR: io not desanitized. Can't save current file.")
return false return false
end end
-- Check default path. -- Check default path.
if Path==nil and not lfs then 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.") BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end end
-- Set path or default. -- Set path or default.
local path = nil local path = nil
if lfs then if lfs then
path=Path or lfs.writedir() path=Path or lfs.writedir()
end end
-- Set file name. -- Set file name.
local filename=Filename local filename=Filename
if path~=nil then if path~=nil then
filename=path.."\\"..filename filename=path.."\\"..filename
end end
-- write -- write
local f = assert(io.open(filename, "wb")) local f = assert(io.open(filename, "wb"))
f:write(Data) f:write(Data)
@ -2477,43 +2484,43 @@ function UTILS.SaveToFile(Path,Filename,Data)
return true return true
end 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 Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file. -- @param #string Filename The name of the file.
-- @return #boolean outcome True if reading is possible and successful, else false. -- @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! -- @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) function UTILS.LoadFromFile(Path,Filename)
-- Thanks to @FunkyFranky -- Thanks to @FunkyFranky
-- Check io module is available. -- Check io module is available.
if not io then if not io then
BASE:E("ERROR: io not desanitized. Can't save current state.") BASE:E("ERROR: io not desanitized. Can't save current state.")
return false return false
end end
-- Check default path. -- Check default path.
if Path==nil and not lfs then 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.") BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end end
-- Set path or default. -- Set path or default.
local path = nil local path = nil
if lfs then if lfs then
path=Path or lfs.writedir() path=Path or lfs.writedir()
end end
-- Set file name. -- Set file name.
local filename=Filename local filename=Filename
if path~=nil then if path~=nil then
filename=path.."\\"..filename filename=path.."\\"..filename
end end
-- Check if file exists. -- Check if file exists.
local exists=UTILS.CheckFileExists(Path,Filename) local exists=UTILS.CheckFileExists(Path,Filename)
if not exists then if not exists then
BASE:I(string.format("ERROR: File %s does not exist!",filename)) BASE:I(string.format("ERROR: File %s does not exist!",filename))
return false return false
end end
-- read -- read
local file=assert(io.open(filename, "rb")) local file=assert(io.open(filename, "rb"))
local loadeddata = {} local loadeddata = {}
@ -2540,30 +2547,30 @@ function UTILS.CheckFileExists(Path,Filename)
return false return false
end end
end end
-- Check io module is available. -- Check io module is available.
if not io then if not io then
BASE:E("ERROR: io not desanitized.") BASE:E("ERROR: io not desanitized.")
return false return false
end end
-- Check default path. -- Check default path.
if Path==nil and not lfs then 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.") BASE:E("WARNING: lfs not desanitized. Loading will look into your DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end end
-- Set path or default. -- Set path or default.
local path = nil local path = nil
if lfs then if lfs then
path=Path or lfs.writedir() path=Path or lfs.writedir()
end end
-- Set file name. -- Set file name.
local filename=Filename local filename=Filename
if path~=nil then if path~=nil then
filename=path.."\\"..filename filename=path.."\\"..filename
end end
-- Check if file exists. -- Check if file exists.
local exists=_fileexists(filename) local exists=_fileexists(filename)
if not exists then if not exists then
@ -2600,7 +2607,7 @@ end
-- @return #boolean outcome True if saving is successful, else false. -- @return #boolean outcome True if saving is successful, else false.
-- @usage -- @usage
-- We will go through the list and find the corresponding group and save the current group size (0 when dead). -- 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. -- 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 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. -- The data will be a simple comma separated list of groupname and size, with one header line.
@ -2639,12 +2646,12 @@ end
-- @return #boolean outcome True if saving is successful, else false. -- @return #boolean outcome True if saving is successful, else false.
-- @usage -- @usage
-- We will go through the set and find the corresponding group and save the current group size and current position. -- 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 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 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. -- cannot be covered with this.
-- **Note** Do NOT use dashes or hashes in group template names (-,#)! -- **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 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) function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
local filename = Filename or "SetOfGroups" local filename = Filename or "SetOfGroups"
local data = "--Save SET of groups: "..Filename .."\n" local data = "--Save SET of groups: "..Filename .."\n"
@ -2659,7 +2666,7 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
end end
if string.find(template,"#") then if string.find(template,"#") then
template = string.gsub(name,"#(%d+)$","") template = string.gsub(name,"#(%d+)$","")
end end
local units = group:CountAliveUnits() local units = group:CountAliveUnits()
local position = group:GetVec3() local position = group:GetVec3()
if Structured then if Structured then
@ -2671,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) data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
else else
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) 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
end end
-- save the data -- save the data
@ -2742,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 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 )` -- @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) function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
local fires = {} local fires = {}
local function Smokers(name,coord,effect,density) local function Smokers(name,coord,effect,density)
local eff = math.random(8) local eff = math.random(8)
if type(effect) == "number" then eff = effect end if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name) coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name) table.insert(fires,name)
end end
local function Cruncher(group,typename,anzahl) local function Cruncher(group,typename,anzahl)
local units = group:GetUnits() local units = group:GetUnits()
local reduced = 0 local reduced = 0
@ -2769,7 +2776,7 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
end end
end end
end end
local reduce = true local reduce = true
if Reduce == false then reduce = false end if Reduce == false then reduce = false end
local filename = Filename or "StateListofGroups" local filename = Filename or "StateListofGroups"
@ -2811,13 +2818,13 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
end end
local reduce = false local reduce = false
if loadednumber < _number then reduce = true end 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 if reduce then
Cruncher(actualgroup,_name,_number-loadednumber) Cruncher(actualgroup,_name,_number-loadednumber)
end end
end end
else else
local reduction = actualgroup:CountAliveUnits() - size local reduction = actualgroup:CountAliveUnits() - size
@ -2832,7 +2839,7 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinema
end end
end end
table.insert(datatable,data) table.insert(datatable,data)
end end
else else
return nil return nil
end end
@ -2847,11 +2854,11 @@ end
-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position. -- @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 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. -- @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 }` -- 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 )` -- @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) function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
local fires = {} local fires = {}
local usedtemplates = {} local usedtemplates = {}
local spawn = true local spawn = true
@ -2859,14 +2866,14 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
local filename = Filename or "SetOfGroups" local filename = Filename or "SetOfGroups"
local setdata = SET_GROUP:New() local setdata = SET_GROUP:New()
local datatable = {} local datatable = {}
local function Smokers(name,coord,effect,density) local function Smokers(name,coord,effect,density)
local eff = math.random(8) local eff = math.random(8)
if type(effect) == "number" then eff = effect end if type(effect) == "number" then eff = effect end
coord:BigSmokeAndFire(eff,density,name) coord:BigSmokeAndFire(eff,density,name)
table.insert(fires,name) table.insert(fires,name)
end end
local function Cruncher(group,typename,anzahl) local function Cruncher(group,typename,anzahl)
local units = group:GetUnits() local units = group:GetUnits()
local reduced = 0 local reduced = 0
@ -2884,7 +2891,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
end end
end end
end end
local function PostSpawn(args) local function PostSpawn(args)
local spwndgrp = args[1] local spwndgrp = args[1]
local size = args[2] local size = args[2]
@ -2894,16 +2901,16 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
local actualsize = spwndgrp:CountAliveUnits() local actualsize = spwndgrp:CountAliveUnits()
if actualsize > size then if actualsize > size then
if Structured and structure then if Structured and structure then
local loadedstructure = {} local loadedstructure = {}
local strcset = UTILS.Split(structure,";") local strcset = UTILS.Split(structure,";")
for _,_data in pairs(strcset) do for _,_data in pairs(strcset) do
local datasplit = UTILS.Split(_data,"==") local datasplit = UTILS.Split(_data,"==")
loadedstructure[datasplit[1]] = tonumber(datasplit[2]) loadedstructure[datasplit[1]] = tonumber(datasplit[2])
end end
local originalstructure = UTILS.GetCountPerTypeName(spwndgrp) local originalstructure = UTILS.GetCountPerTypeName(spwndgrp)
for _name,_number in pairs(originalstructure) do for _name,_number in pairs(originalstructure) do
local loadednumber = 0 local loadednumber = 0
if loadedstructure[_name] then if loadedstructure[_name] then
@ -2911,11 +2918,11 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
end end
local reduce = false local reduce = false
if loadednumber < _number then reduce = true end if loadednumber < _number then reduce = true end
if reduce then if reduce then
Cruncher(spwndgrp,_name,_number-loadednumber) Cruncher(spwndgrp,_name,_number-loadednumber)
end end
end end
else else
local reduction = actualsize-size local reduction = actualsize-size
@ -2928,16 +2935,16 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
end end
end end
end end
local function MultiUse(Data) 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 if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then
-- multispawn -- multispawn
if not usedtemplates[template].done then if not usedtemplates[template].done then
local spwnd = 0 local spwnd = 0
local spawngrp = SPAWN:New(template) local spawngrp = SPAWN:New(template)
spawngrp:InitLimit(0,usedtemplates[template].used) 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 spwnd = spwnd + 1
local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd) local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
@ -2949,7 +2956,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
return false return false
end end
end end
--BASE:I("Spawn = "..tostring(spawn)) --BASE:I("Spawn = "..tostring(spawn))
if UTILS.CheckFileExists(Path,filename) then if UTILS.CheckFileExists(Path,filename) then
local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename)
@ -2983,13 +2990,13 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D
end end
end 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 if spawn and not MultiUse(_entry) and _entry.size > 0 then
local group = SPAWN:New(_entry.template) local group = SPAWN:New(_entry.template)
local sgrp=group:SpawnFromCoordinate(_entry.coordinate) local sgrp=group:SpawnFromCoordinate(_entry.coordinate)
BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure})
end end
end end
else else
return nil return nil
end end
@ -3018,7 +3025,7 @@ function UTILS.LoadSetOfStatics(Path,Filename)
if StaticObject then if StaticObject then
datatable:AddObject(StaticObject) datatable:AddObject(StaticObject)
end end
end end
else else
return nil return nil
end end
@ -3034,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 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. -- @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 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. -- Returns nil when file cannot be read.
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density) function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
local fires = {} local fires = {}
@ -3070,7 +3077,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,E
if Cinematic then if Cinematic then
local effect = math.random(8) local effect = math.random(8)
if type(Effect) == "number" then if type(Effect) == "number" then
effect = Effect effect = Effect
end end
coord:BigSmokeAndFire(effect,Density,staticname) coord:BigSmokeAndFire(effect,Density,staticname)
table.insert(fires,staticname) table.insert(fires,staticname)
@ -3080,7 +3087,7 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,E
end end
end end
end end
end end
else else
return nil return nil
end end
@ -3128,10 +3135,10 @@ function UTILS.ToStringBRAANATO(FromGrp,ToGrp)
if aspect == "" then if aspect == "" then
BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track) BRAANATO = string.format("%s, BRA, %03d, %d miles, Angels %d, Track %s",GroupWords,bearing, rangeNM, alt, track)
else 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
end end
return BRAANATO return BRAANATO
end end
--- Check if an object is contained in a table. --- Check if an object is contained in a table.
@ -3176,7 +3183,7 @@ function UTILS.IsAnyInTable(Table, Objects, Key)
end end
end end
end end
end end
return false return false
@ -3192,30 +3199,30 @@ end
-- @param #table Color Color of the line in RGB, e.g. {1,0,0} for red -- @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 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 #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) function UTILS.PlotRacetrack(Coordinate, Altitude, Speed, Heading, Leg, Coalition, Color, Alpha, LineType, ReadOnly)
local fix_coordinate = Coordinate local fix_coordinate = Coordinate
local altitude = Altitude local altitude = Altitude
local speed = Speed or 350 local speed = Speed or 350
local heading = Heading or 270 local heading = Heading or 270
local leg_distance = Leg or 10 local leg_distance = Leg or 10
local coalition = Coalition or -1 local coalition = Coalition or -1
local color = Color or {1,0,0} local color = Color or {1,0,0}
local alpha = Alpha or 1 local alpha = Alpha or 1
local lineType = LineType or 1 local lineType = LineType or 1
speed = UTILS.IasToTas(speed, UTILS.FeetToMeters(altitude), oatcorr) speed = UTILS.IasToTas(speed, UTILS.FeetToMeters(altitude), oatcorr)
local turn_radius = 0.0211 * speed -3.01 local turn_radius = 0.0211 * speed -3.01
local point_two = fix_coordinate:Translate(UTILS.NMToMeters(leg_distance), heading, true, false) 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_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 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_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) 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) fix_coordinate:LineToAll(point_two, coalition, color, alpha, lineType)
point_four:LineToAll(point_three, coalition, color, alpha, lineType) point_four:LineToAll(point_three, coalition, color, alpha, lineType)
@ -3928,7 +3935,7 @@ function UTILS.MGRSStringToSRSFriendly(Text,Slow)
Text = string.gsub(Text,"9","niner") Text = string.gsub(Text,"9","niner")
if Slow then if Slow then
Text = '<prosody rate="slow">'..Text..'</prosody>' Text = '<prosody rate="slow">'..Text..'</prosody>'
end end
Text = "MGRS;"..Text Text = "MGRS;"..Text
return Text return Text
end end

View File

@ -722,35 +722,35 @@ AIRBASE.Sinai = {
--- Airbases of the Kola map --- Airbases of the Kola map
-- --
-- * AIRBASE.Kola.Banak
-- * AIRBASE.Kola.Bas_100 -- * AIRBASE.Kola.Bas_100
-- * AIRBASE.Kola.Bodo -- * AIRBASE.Kola.Bodo
-- * AIRBASE.Kola.Jokkmokk -- * AIRBASE.Kola.Jokkmokk
-- * AIRBASE.Kola.Kalixfors -- * AIRBASE.Kola.Kalixfors
-- * AIRBASE.Kola.Kemi_Tornio -- * AIRBASE.Kola.Kemi_Tornio
-- * AIRBASE.Kola.Kiruna -- * AIRBASE.Kola.Kiruna
-- * AIRBASE.Kola.Lakselv
-- * AIRBASE.Kola.Monchegorsk -- * AIRBASE.Kola.Monchegorsk
-- * AIRBASE.Kola.Murmansk_International -- * AIRBASE.Kola.Murmansk_International
-- * AIRBASE.Kola.Olenegorsk -- * AIRBASE.Kola.Olenya
-- * AIRBASE.Kola.Rovaniemi -- * AIRBASE.Kola.Rovaniemi
-- * AIRBASE.Kola.Severomorsk1 -- * AIRBASE.Kola.Severomorsk_1
-- * AIRBASE.Kola.Severomorsk3 -- * AIRBASE.Kola.Severomorsk_3
-- --
-- @field Kola -- @field Kola
AIRBASE.Kola = { AIRBASE.Kola = {
["Banak"] = "Banak",
["Bas_100"] = "Bas 100", ["Bas_100"] = "Bas 100",
["Bodo"] = "Bodo", ["Bodo"] = "Bodo",
["Jokkmokk"] = "Jokkmokk", ["Jokkmokk"] = "Jokkmokk",
["Kalixfors"] = "Kalixfors", ["Kalixfors"] = "Kalixfors",
["Kemi_Tornio"] = "Kemi Tornio", ["Kemi_Tornio"] = "Kemi Tornio",
["Kiruna"] = "Kiruna", ["Kiruna"] = "Kiruna",
["Lakselv"] = "Lakselv",
["Monchegorsk"] = "Monchegorsk", ["Monchegorsk"] = "Monchegorsk",
["Murmansk_International"] = "Murmansk International", ["Murmansk_International"] = "Murmansk International",
["Olenegorsk"] = "Olenegorsk", ["Olenya"] = "Olenya",
["Rovaniemi"] = "Rovaniemi", ["Rovaniemi"] = "Rovaniemi",
["Severomorsk1"] = "Severomorsk1", ["Severomorsk_1"] = "Severomorsk-1",
["Severomorsk3"] = "Severomorsk3", ["Severomorsk_3"] = "Severomorsk-3",
} }
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".

View File

@ -1207,15 +1207,17 @@ function GROUP:GetCoordinate()
-- no luck, try the API way -- no luck, try the API way
local DCSGroup = Group.getByName(self.GroupName) local DCSGroup = Group.getByName(self.GroupName)
local DCSUnits = DCSGroup:getUnits() or {} if DCSGroup then
for _,_unit in pairs(DCSUnits) do local DCSUnits = DCSGroup:getUnits() or {}
if Object.isExist(_unit) then for _,_unit in pairs(DCSUnits) do
local position = _unit:getPosition() if Object.isExist(_unit) then
local point = position.p ~= nil and position.p or _unit:GetPoint() local position = _unit:getPosition()
if point then local point = position.p ~= nil and position.p or _unit:GetPoint()
--self:I(point) if point then
local coord = COORDINATE:NewFromVec3(point) --self:I(point)
return coord local coord = COORDINATE:NewFromVec3(point)
return coord
end
end end
end end
end end