Merge branch 'master' into FF/MasterDevel

This commit is contained in:
Frank
2023-01-28 11:09:08 +01:00
31 changed files with 2636 additions and 653 deletions

View File

@@ -93,6 +93,8 @@
-- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used.
-- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights
-- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players.
-- @field #string SRSText Text of the complete SRS message (if done at least once, else nil)
-- @field #boolean ATISforFARPs Will be set to true if the base given is a FARP/Helipad
-- @extends Core.Fsm#FSM
--- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde
@@ -268,6 +270,8 @@
-- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before
-- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds.
--
-- An SRS Setup-Guide can be found here: [Moose TTS Setup Guide](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf)
--
-- # Examples
--
-- ## Caucasus: Batumi
@@ -306,6 +310,19 @@
-- atis:Start()
--
-- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux).
--
-- ## FARPS
--
-- ATIS is working with FARPS, but this requires the usage of SRS. The airbase name for the `New()-method` is the UNIT name of the FARP:
--
-- atis = ATIS:New("FARP Gold",119,radio.modulation.AM)
-- atis:SetMetricUnits()
-- atis:SetTransmitOnlyWithPlayers(true)
-- atis:SetReportmBar(true)
-- atis:SetTowerFrequencies(127.50)
-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US",nil,5002)
-- atis:SetAdditionalInformation("Welcome to the Jungle!")
-- atis:__Start(3)
--
-- @field #ATIS
ATIS = {
@@ -348,6 +365,7 @@ ATIS = {
relHumidity = nil,
ReportmBar = false,
TransmitOnlyWithPlayers = false,
ATISforFARPs = false,
}
--- NATO alphabet.
@@ -590,7 +608,7 @@ _ATIS = {}
--- ATIS class version.
-- @field #string version
ATIS.version = "0.9.11"
ATIS.version = "0.9.14"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@@ -616,7 +634,7 @@ ATIS.version = "0.9.11"
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new ATIS class object for a specific aircraft carrier unit.
--- Create a new ATIS class object for a specific airbase.
-- @param #ATIS self
-- @param #string AirbaseName Name of the airbase.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
@@ -878,6 +896,13 @@ function ATIS:SetMapMarks( switch )
return self
end
--- Return the complete SRS Text block, if at least generated once. Else nil.
-- @param #ATIS self
-- @return #string SRSText
function ATIS:GetSRSText()
return self.SRSText
end
--- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°.
-- @param #ATIS self
-- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended.
@@ -1039,7 +1064,7 @@ end
--
-- * 186° on the Caucaus map
-- * 192° on the Nevada map
-- * 170° on the Normany map
-- * 170° on the Normandy map
-- * 182° on the Persian Gulf map
--
-- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation.
@@ -1247,11 +1272,18 @@ end
function ATIS:onafterStart( From, Event, To )
-- Check that this is an airdrome.
if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) )
if self.airbase:GetAirbaseCategory() == Airbase.Category.SHIP then
self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.", self.airbasename ) )
return
end
-- Check that if is a Helipad.
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
self:E( self.lid .. string.format( "EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON", self.airbasename ) )
self.ATISforFARPs = true
self.useSRS = true
end
-- Info.
self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) )
@@ -1463,10 +1495,19 @@ function ATIS:onafterBroadcast( From, Event, To )
--------------
--- Runway ---
--------------
local runwayLanding, rwyLandingLeft=self:GetActiveRunway()
local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
local runwayLanding, rwyLandingLeft
local runwayTakeoff, rwyTakeoffLeft
if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then
runwayLanding, rwyLandingLeft="PAD 01",false
runwayTakeoff, rwyTakeoffLeft="PAD 02",false
else
runwayLanding, rwyLandingLeft=self:GetActiveRunway()
runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
end
------------
--- Time ---
------------
@@ -1780,7 +1821,7 @@ function ATIS:onafterBroadcast( From, Event, To )
-- Airbase name
subtitle = string.format( "%s", self.airbasename )
if self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
if (not self.ATISforFARPs) and self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then
subtitle = subtitle .. " Airport"
end
if not self.useSRS then
@@ -1855,8 +1896,6 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
alltext = alltext .. ";\n" .. subtitle
--self:I("Line 1811")
--self:I(alltext)
-- Visibility
if self.metric then
@@ -1874,8 +1913,6 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
alltext = alltext .. ";\n" .. subtitle
--self:I("Line 1830")
--self:I(alltext)
subtitle = ""
-- Weather phenomena
@@ -1977,10 +2014,8 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
end
--self:I("Line 1932")
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
subtitle = ""
-- Temperature
if self.TDegF then
@@ -2009,9 +2044,7 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
end
end
--self:I("Line 1962")
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
-- Dew point
if self.TDegF then
@@ -2040,8 +2073,6 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 )
end
end
--self:I("Line 1992")
--self:I(alltext)
alltext = alltext .. ";\n" .. subtitle
-- Altimeter QNH/QFE.
@@ -2107,69 +2138,68 @@ function ATIS:onafterBroadcast( From, Event, To )
end
end
end
--self:I("Line 2049")
--self:I(alltext)
alltext = alltext .. ";\n" .. subtitle
-- Active runway.
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
subtitle=subtitle.." Right"
end
local _RUNACT = subtitle
if not self.useSRS then
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if not self.ATISforFARPs then
-- Active runway.
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
subtitle=subtitle.." Left"
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
subtitle=subtitle.." Right"
end
end
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
local length = runact.length
if not self.metric then
length = UTILS.MetersToFeet( length )
end
-- Length in thousands and hundrets of ft/meters.
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
-- Subtitle.
local subtitle = string.format( "Runway length %d", length )
if self.metric then
subtitle = subtitle .. " meters"
else
subtitle = subtitle .. " feet"
end
-- Transmit.
local _RUNACT = subtitle
if not self.useSRS then
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
if tonumber( L1000 ) > 0 then
self.radioqueue:Number2Transmission( L1000 )
self:Transmission( ATIS.Sound.Thousand, 0.1 )
end
if tonumber( L0100 ) > 0 then
self.radioqueue:Number2Transmission( L0100 )
self:Transmission( ATIS.Sound.Hundred, 0.1 )
end
if self.metric then
self:Transmission( ATIS.Sound.Meters, 0.1 )
else
self:Transmission( ATIS.Sound.Feet, 0.1 )
self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle)
self.radioqueue:Number2Transmission(runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
end
end
alltext = alltext .. ";\n" .. subtitle
-- Runway length.
if self.rwylength then
local runact = self.airbase:GetActiveRunway( self.runwaym2t )
local length = runact.length
if not self.metric then
length = UTILS.MetersToFeet( length )
end
-- Length in thousands and hundrets of ft/meters.
local L1000, L0100 = self:_GetThousandsAndHundreds( length )
-- Subtitle.
local subtitle = string.format( "Runway length %d", length )
if self.metric then
subtitle = subtitle .. " meters"
else
subtitle = subtitle .. " feet"
end
-- Transmit.
if not self.useSRS then
self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle )
if tonumber( L1000 ) > 0 then
self.radioqueue:Number2Transmission( L1000 )
self:Transmission( ATIS.Sound.Thousand, 0.1 )
end
if tonumber( L0100 ) > 0 then
self.radioqueue:Number2Transmission( L0100 )
self:Transmission( ATIS.Sound.Hundred, 0.1 )
end
if self.metric then
self:Transmission( ATIS.Sound.Meters, 0.1 )
else
self:Transmission( ATIS.Sound.Feet, 0.1 )
end
end
alltext = alltext .. ";\n" .. subtitle
end
end
-- Airfield elevation
if self.elevation then
@@ -2236,9 +2266,7 @@ function ATIS:onafterBroadcast( From, Event, To )
end
-- ILS
--self:I({ils=self.ils})
local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft)
--self:I({ils=ils,runwayLanding=runwayLanding, rwyLandingLeft=rwyLandingLeft})
if ils then
subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency )
if not self.useSRS then
@@ -2253,7 +2281,6 @@ function ATIS:onafterBroadcast( From, Event, To )
self:Transmission( ATIS.Sound.MegaHertz, 0.2 )
end
alltext = alltext .. ";\n" .. subtitle
--self:I(alltext)
end
-- Outer NDB
@@ -2389,6 +2416,8 @@ function ATIS:onafterReport( From, Event, To, Text )
local text = string.gsub( text, "mmHg", "millimeters of Mercury" )
local text = string.gsub( text, "hPa", "hectopascals" )
local text = string.gsub( text, "m/s", "meters per second" )
local text = string.gsub( text, "TACAN", "tackan" )
local text = string.gsub( text, "FARP", "farp" )
-- Replace ";" by "."
local text = string.gsub( text, ";", " . " )
@@ -2400,7 +2429,8 @@ function ATIS:onafterReport( From, Event, To, Text )
local duration = STTS.getSpeechTime(text,0.95)
self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2)
--self.msrs:PlayText( text )
self.SRSText = text
end
end

View File

@@ -26,11 +26,11 @@
--
-- ===
--
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing)
-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), The Chosen One (Persistence)
-- @module Ops.CSAR
-- @image OPS_CSAR.jpg
-- Date: November 2022
-- Date: January 2023
-------------------------------------------------------------------------
--- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM
@@ -197,6 +197,26 @@
--
-- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat
-- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE)
--
-- ## 6. Save and load downed pilots - Persistance
--
-- You can save and later load back downed pilots to make your mission persistent.
-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts.
-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you.
--
-- Use the following options to manage your saves:
--
-- mycsar.enableLoadSave = true -- allow auto-saving and loading of files
-- mycsar.saveinterval = 600 -- save every 10 minutes
-- mycsar.filename = "missionsave.csv" -- example filename
-- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path
--
-- Then use an initial load at the beginning of your mission:
--
-- mycsar:__Load(10)
--
-- **Caveat:**
-- Dropped troop noMessage and forcedesc parameters aren't saved.
--
-- @field #CSAR
CSAR = {
@@ -272,7 +292,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2
--- CSAR class version.
-- @field #string version
CSAR.version="1.0.16"
CSAR.version="1.0.17"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list
@@ -296,6 +316,8 @@ function CSAR:New(Coalition, Template, Alias)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #CSAR
BASE:T({Coalition, Prefixes, Alias})
--set Coalition
if Coalition and type(Coalition)=="string" then
if Coalition=="blue" then
@@ -346,6 +368,8 @@ function CSAR:New(Coalition, Template, Alias)
self:AddTransition("*", "Returning", "*") -- CSAR able to return to base.
self:AddTransition("*", "Rescued", "*") -- Pilot at MASH.
self:AddTransition("*", "KIA", "*") -- Pilot killed in action.
self:AddTransition("*", "Load", "*") -- CSAR load event.
self:AddTransition("*", "Save", "*") -- CSAR save event.
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
-- tables, mainly for tracking actions
@@ -442,6 +466,14 @@ function CSAR:New(Coalition, Template, Alias)
self.SRSVolume = 1.0 -- volume 0.0 to 1.0
self.SRSGender = "male" -- male or female
local AliaS = string.gsub(self.alias," ","_")
self.filename = string.format("CSAR_%s_Persist.csv",AliaS)
-- load and save downed pilots
self.enableLoadSave = false
self.filepath = nil
self.saveinterval = 600
------------------------
--- Pseudo Functions ---
------------------------
@@ -471,6 +503,24 @@ function CSAR:New(Coalition, Template, Alias)
-- @function [parent=#CSAR] __Status
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--
-- --- Triggers the FSM event "Load".
-- @function [parent=#CSAR] Load
-- @param #CSAR self
--- Triggers the FSM event "Load" after a delay.
-- @function [parent=#CSAR] __Load
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Save".
-- @function [parent=#CSAR] Load
-- @param #CSAR self
--- Triggers the FSM event "Save" after a delay.
-- @function [parent=#CSAR] __Save
-- @param #CSAR self
-- @param #number delay Delay in seconds.
--- On After "PilotDown" event. Downed Pilot detected.
-- @function [parent=#CSAR] OnAfterPilotDown
@@ -538,6 +588,24 @@ function CSAR:New(Coalition, Template, Alias)
-- @param #string To To state.
-- @param #string Pilotname Name of the pilot KIA.
--- FSM Function OnAfterLoad.
-- @function [parent=#CSAR] OnAfterLoad
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
--- FSM Function OnAfterSave.
-- @function [parent=#CSAR] OnAfterSave
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
return self
end
@@ -850,7 +918,7 @@ end
--- (Internal) Function to add a CSAR object into the scene at a Point coordinate (VEC_2). For mission designers wanting to add e.g. casualties to the scene, that don't use beacons.
-- @param #CSAR self
-- @param #string _Point a POINT_VEC2.
-- @param Core.Point#COORDINATE _Point
-- @param #number _coalition Coalition.
-- @param #string _description (optional) Description.
-- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR.
@@ -883,7 +951,7 @@ end
--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene.
-- @param #CSAR self
-- @param #string Point a POINT_VEC2.
-- @param Core.Point#COORDINATE Point
-- @param #number Coalition Coalition.
-- @param #string Description (optional) Description.
-- @param #boolean addBeacon (optional) yes or no.
@@ -893,8 +961,8 @@ end
-- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names.
-- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so:
--
-- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition.
-- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE )
-- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition.
-- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE )
function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc)
return self
@@ -2108,9 +2176,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq)
local _radioUnit = _group:GetUnit(1)
if _radioUnit then
local Frequency = _freq -- Freq in Hertz
local name = _radioUnit:GetName()
local Sound = "l10n/DEFAULT/"..self.radioSound
local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0}
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight
trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight
end
end
return self
@@ -2218,7 +2287,16 @@ function CSAR:onafterStart(From, Event, To)
self.msrs:SetLabel("CSAR")
self.SRSQueue = MSRSQUEUE:New("CSAR")
end
self:__Status(-10)
if self.enableLoadSave then
local interval = self.saveinterval
local filename = self.filename
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
@@ -2451,6 +2529,240 @@ function CSAR:onbeforeLanded(From, Event, To, HeliName, Airbase)
self:T({From, Event, To, HeliName, Airbase})
return self
end
--- On before "Save" event. Checks if io and lfs are available.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for saving. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onbeforeSave(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
-- Thanks to @FunkyFranky
-- Check io module is available.
if not io then
self:E(self.lid.."ERROR: io not desanitized. Can't save current state.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
return true
end
--- On after "Save" event. Player data is saved to file.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized.
-- @param #string filename (Optional) File name for saving. Default is Default is "CSAR_<alias>_Persist.csv".
function CSAR:onafterSave(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
-- Thanks to @FunkyFranky
if not self.enableLoadSave then
return self
end
--- Function that saves data to file
local function _savefile(filename, data)
local f = assert(io.open(filename, "wb"))
f:write(data)
f:close()
end
-- Set path or default.
if lfs then
path=self.filepath or lfs.writedir()
end
-- Set file name.
filename=filename or self.filename
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
local pilots = self.downedPilots
--local data = "LoadedData = {\n"
local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n"
local n = 0
for _,_grp in pairs(pilots) do
local DownedPilot = _grp -- Wrapper.Group#GROUP
if DownedPilot and DownedPilot.alive then
-- get downed pilot data for saving
local playerName = DownedPilot.player
local group = DownedPilot.group
local coalition = group:GetCoalition()
local country = group:GetCountry()
local description = DownedPilot.desc
local typeName = DownedPilot.typename
local freq = DownedPilot.frequency
local location = group:GetVec3()
local unitName = DownedPilot.originalUnit
local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq)
self:I(self.lid.."Saving to CSAR File: " .. txt)
data = data .. txt
end
end
_savefile(filename, data)
-- AutoSave
if self.enableLoadSave then
local interval = self.saveinterval
local filename = self.filename
local filepath = self.filepath
self:__Save(interval,filepath,filename)
end
return self
end
--- On before "Load" event. Checks if io and lfs and the file are available.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onbeforeLoad(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
--- Function that check if a file exists.
local function _fileexists(name)
local f=io.open(name,"r")
if f~=nil then
io.close(f)
return true
else
return false
end
end
-- Set file name and path
filename=filename or self.filename
path = path or self.filepath
-- Check io module is available.
if not io then
self:E(self.lid.."WARNING: io not desanitized. Cannot load file.")
return false
end
-- Check default path.
if path==nil and not lfs then
self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.")
end
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Check if file exists.
local exists=_fileexists(filename)
if exists then
return true
else
self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename))
return false
--return self
end
end
--- On after "Load" event. Loads dropped units from file.
-- @param #CSAR self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized.
-- @param #string filename (Optional) File name for loading. Default is "CSAR_<alias>_Persist.csv".
function CSAR:onafterLoad(From, Event, To, path, filename)
self:T({From, Event, To, path, filename})
if not self.enableLoadSave then
return self
end
--- Function that loads data from a file.
local function _loadfile(filename)
local f=assert(io.open(filename, "rb"))
local data=f:read("*all")
f:close()
return data
end
-- Set file name and path
filename=filename or self.filename
path = path or self.filepath
-- Set path or default.
if lfs then
path=path or lfs.writedir()
end
-- Set path.
if path~=nil then
filename=path.."\\"..filename
end
-- Info message.
local text=string.format("Loading CSAR state from file %s", filename)
MESSAGE:New(text,10):ToAllIf(self.Debug)
self:I(self.lid..text)
local file=assert(io.open(filename, "rb"))
local loadeddata = {}
for line in file:lines() do
loadeddata[#loadeddata+1] = line
end
file:close()
-- remove header
table.remove(loadeddata, 1)
for _id,_entry in pairs (loadeddata) do
local dataset = UTILS.Split(_entry,",")
-- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n
local playerName = dataset[1]
local vec3 = {}
vec3.x = tonumber(dataset[2])
vec3.y = tonumber(dataset[3])
vec3.z = tonumber(dataset[4])
local point = COORDINATE:NewFromVec3(vec3)
local coalition = dataset[5]
local country = dataset[6]
local description = dataset[7]
local typeName = dataset[8]
local unitName = dataset[9]
local freq = dataset[10]
self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil)
end
return self
end
--------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- End Ops.CSAR
--------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@@ -22,7 +22,7 @@
-- @module Ops.CTLD
-- @image OPS_CTLD.jpg
-- Last Update October 2022
-- Last Update Jan 2023
do
@@ -711,6 +711,9 @@ do
-- my_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes
-- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types.
-- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted.
-- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true`
-- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds.
-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects
--
-- ## 2.1 User functions
--
@@ -966,7 +969,110 @@ do
--
-- **Caveat:**
-- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved.
--
-- ## 7. Complex example - Build a complete FARP from a CTLD crate drop
--
-- Prerequisites - you need to add a cargo of type FOB to your CTLD instance, for simplification reasons we call it FOB:
--
-- my_ctld:AddCratesCargo("FARP",{"FOB"},CTLD_CARGO.Enum.FOB,2)
--
-- Also, you need to have **all statics with the fitting names** as per the script in your mission already, as we're going to copy them, and a template
-- for FARP vehicles, so -- services are goin to work (e.g. for the blue side: an unarmed humvee, two trucks and a fuel truck. Optionally add a fire fighter).
--
-- The following code will build a FARP at the coordinate the FOB was dropped and built:
--
-- -- FARP Radio. First one has 130AM, next 131 and for forth
-- local FARPFreq = 130
-- local FARPName = 1 -- numbers 1..10
--
-- local FARPClearnames = {
-- [1]="London",
-- [2]="Dallas",
-- [3]="Paris",
-- [4]="Moscow",
-- [5]="Berlin",
-- [6]="Rome",
-- [7]="Madrid",
-- [8]="Warsaw",
-- [9]="Dublin",
-- [10]="Perth",
-- }
--
-- function BuildAFARP(Coordinate)
-- local coord = Coordinate -- Core.Point#COORDINATE
--
-- local FarpName = ((FARPName-1)%10)+1
-- local FName = FARPClearnames[FarpName]
--
-- FARPFreq = FARPFreq + 1
-- FARPName = FARPName + 1
--
-- -- Create a SPAWNSTATIC object from a template static FARP object.
-- local SpawnStaticFarp=SPAWNSTATIC:NewFromStatic("Static Invisible FARP-1", country.id.USA)
--
-- -- Spawning FARPs is special in DCS. Therefore, we need to specify that this is a FARP. We also set the callsign and the frequency.
-- SpawnStaticFarp:InitFARP(FARPName, FARPFreq, 0)
-- SpawnStaticFarp:InitDead(false)
--
-- -- Spawn FARP
-- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),160,false)
-- local Heading = 0
-- local FarpBerlin=SpawnStaticFarp:SpawnFromZone(ZoneSpawn, Heading, "FARP "..FName)
--
-- -- ATC and services - put them 125m from the center of the zone towards North
-- local FarpVehicles = SPAWN:NewWithAlias("FARP Vehicles Template","FARP "..FName.." Technicals")
-- FarpVehicles:InitHeading(180)
-- local FarpVCoord = coord:Translate(125,0)
-- FarpVehicles:SpawnFromCoordinate(FarpVCoord)
--
-- -- We will put the rest of the statics in a nice circle around the center
-- local base = 330
-- local delta = 30
--
-- local windsock = SPAWNSTATIC:NewFromStatic("Static Windsock-1",country.id.USA)
-- local sockcoord = coord:Translate(125,base)
-- windsock:SpawnFromCoordinate(sockcoord,Heading,"Windsock "..FName)
-- base=base-delta
--
-- local fueldepot = SPAWNSTATIC:NewFromStatic("Static FARP Fuel Depot-1",country.id.USA)
-- local fuelcoord = coord:Translate(125,base)
-- fueldepot:SpawnFromCoordinate(fuelcoord,Heading,"Fueldepot "..FName)
-- base=base-delta
--
-- local ammodepot = SPAWNSTATIC:NewFromStatic("Static FARP Ammo Storage-2-1",country.id.USA)
-- local ammocoord = coord:Translate(125,base)
-- ammodepot:SpawnFromCoordinate(ammocoord,Heading,"Ammodepot "..FName)
-- base=base-delta
--
-- local CommandPost = SPAWNSTATIC:NewFromStatic("Static FARP Command Post-1",country.id.USA)
-- local CommandCoord = coord:Translate(125,base)
-- CommandPost:SpawnFromCoordinate(CommandCoord,Heading,"Command Post "..FName)
-- base=base-delta
--
-- local Tent1 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA)
-- local Tent1Coord = coord:Translate(125,base)
-- Tent1:SpawnFromCoordinate(Tent1Coord,Heading,"Command Tent "..FName)
-- base=base-delta
--
-- local Tent2 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA)
-- local Tent2Coord = coord:Translate(125,base)
-- Tent2:SpawnFromCoordinate(Tent2Coord,Heading,"Command Tent2 "..FName)
--
-- -- add a loadzone to CTLD
-- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true)
-- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue()
-- end
--
-- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle)
-- local name = Vehicle:GetName()
-- if string.match(name,"FOB",1,true) then
-- local Coord = Vehicle:GetCoordinate()
-- Vehicle:Destroy(false)
-- BuildAFARP(Coord)
-- end
-- end
--
--
-- @field #CTLD
CTLD = {
ClassName = "CTLD",
@@ -1015,7 +1121,16 @@ CTLD = {
-- @type CTLD.ZoneBeacon
-- @field #string name -- Name of zone for the coordinate
-- @field #number frequency -- in mHz
-- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM
-- @field #number modulation -- i.e.CTLD.RadioModulation.FM or CTLD.RadioModulation.AM
--- Radio Modulation
-- @type CTLD.RadioModulation
-- @field #number AM
-- @field #number FM
CTLD.RadioModulation = {
AM = 0,
FM = 1,
}
--- Zone Info.
-- @type CTLD.CargoZone
@@ -1067,6 +1182,7 @@ CTLD.UnitTypes = {
["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000},
["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
["Ka-50_3"] = {type="Ka-50_3", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0},
["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700},
["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers.
@@ -1078,7 +1194,7 @@ CTLD.UnitTypes = {
--- CTLD class version.
-- @field #string version
CTLD.version="1.0.19"
CTLD.version="1.0.27"
--- Instantiate a new CTLD.
-- @param #CTLD self
@@ -1161,6 +1277,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
-- radio beacons
self.RadioSound = "beacon.ogg"
self.RadioPath = "l10n/DEFAULT/"
-- zones stuff
self.pickupZones = {}
@@ -1243,6 +1360,11 @@ function CTLD:New(Coalition, Prefixes, Alias)
self.usesubcats = false
self.subcats = {}
-- disallow building in loadzones
self.nobuildinloadzones = true
self.movecratesbeforebuild = true
self.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER}
local AliaS = string.gsub(self.alias," ","_")
self.filename = string.format("CTLD_%s_Persist.csv",AliaS)
@@ -1635,12 +1757,54 @@ function CTLD:_SendMessage(Text, Time, Clearscreen, Group)
return self
end
--- (Internal) Find a troops CTLD_CARGO object in stock
-- @param #CTLD self
-- @param #string Name of the object
-- @return #CTLD_CARGO Cargo object, nil if it cannot be found
function CTLD:_FindTroopsCargoObject(Name)
self:T(self.lid .. " _FindTroopsCargoObject")
local cargo = nil
for _,_cargo in pairs(self.Cargo_Troops)do
local cargo = _cargo -- #CTLD_CARGO
if cargo.Name == Name then
return cargo
end
end
return nil
end
--- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot!
-- @param #CTLD self
-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object
-- @param #string Troopname The name of the Troops to be loaded. Must be created prior in the CTLD setup!
-- @return #CTLD self
-- @usage
-- local client = UNIT:FindByName("Helo-1-1")
-- if client and client:IsAlive() then
-- myctld:PreloadTroops(client,"Infantry")
-- end
function CTLD:PreloadTroops(Unit,Troopname)
self:T(self.lid .. " PreloadTroops")
local name = Troopname or "Unknown"
if Unit and Unit:IsAlive() then
local cargo = self:_FindTroopsCargoObject(name)
local group = Unit:GetGroup()
if cargo then
self:_LoadTroops(group,Unit,cargo,true)
else
self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!")
end
end
return self
end
--- (Internal) Function to load troops into a heli.
-- @param #CTLD self
-- @param Wrapper.Group#GROUP Group
-- @param Wrapper.Unit#UNIT Unit
-- @param #CTLD_CARGO Cargotype
function CTLD:_LoadTroops(Group, Unit, Cargotype)
-- @param #boolean Inject
function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject)
self:T(self.lid .. " _LoadTroops")
-- check if we have stock
local instock = Cargotype:GetStock()
@@ -1648,7 +1812,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype)
local cgotype = Cargotype:GetType()
local cgonetmass = Cargotype:GetNetMass()
local maxloadable = self:_GetMaxLoadableMass(Unit)
if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then
if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 and not Inject then
-- nothing left over
self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group)
return self
@@ -1656,21 +1820,22 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype)
-- landed or hovering over load zone?
local grounded = not self:IsUnitInAir(Unit)
local hoverload = self:CanHoverLoad(Unit)
--local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors
-- check if we are in LOAD zone
local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if not inzone then
inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP)
end
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group)
if not self.debug then return self end
elseif not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!", 10, false, Group)
if not self.debug then return self end
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group)
if not self.debug then return self end
if not Inject then
if not inzone then
self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group)
if not self.debug then return self end
elseif not grounded and not hoverload then
self:_SendMessage("You need to land or hover in position to load!", 10, false, Group)
if not self.debug then return self end
elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then
self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group)
if not self.debug then return self end
end
end
-- load troops into heli
local group = Group -- Wrapper.Group#GROUP
@@ -2093,10 +2258,11 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop)
self.CargoCounter = self.CargoCounter + 1
local realcargo = nil
if drop then
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat)
--CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory)
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat)
table.insert(droppedcargo,realcargo)
else
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat)
realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat)
Cargo:RemoveStock()
end
table.insert(self.Spawned_Cargo, realcargo)
@@ -2824,6 +2990,14 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
return self
end
end
if not Engineering and self.nobuildinloadzones then
-- are we in a load zone?
local inloadzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD)
if inloadzone then
self:_SendMessage("You cannot build in a loading area, Pilot!", 10, false, Group)
return self
end
end
-- get nearby crates
local finddist = self.CrateDistance or 35
local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true) -- #table
@@ -2834,7 +3008,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
-- get dropped crates
for _,_crate in pairs(crates) do
local Crate = _crate -- #CTLD_CARGO
if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then
if (Crate:WasDropped() or not self.movecratesbeforebuild) and not Crate:IsRepair() and not Crate:IsStatic() then
-- we can build these - maybe
local name = Crate:GetName()
local required = Crate:GetCratesNeeded()
@@ -2879,7 +3053,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering)
local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok)
report:Add(text)
end -- end list buildables
if not foundbuilds then report:Add(" --- None Found ---") end
if not foundbuilds then
report:Add(" --- None found! ---")
if self.movecratesbeforebuild then
report:Add("*** Crates need to be moved before building!")
end
end
report:Add("------------------------------------------------------------")
local text = report:Text()
if not Engineering then
@@ -3440,7 +3619,7 @@ function CTLD:_GetFMBeacon(Name)
table.insert(self.UsedFMFrequencies, FM)
beacon.name = Name
beacon.frequency = FM / 1000000
beacon.modulation = radio.modulation.FM
beacon.modulation = CTLD.RadioModulation.FM
return beacon
end
@@ -3460,7 +3639,7 @@ function CTLD:_GetUHFBeacon(Name)
table.insert(self.UsedUHFFrequencies, UHF)
beacon.name = Name
beacon.frequency = UHF / 1000000
beacon.modulation = radio.modulation.AM
beacon.modulation = CTLD.RadioModulation.AM
return beacon
end
@@ -3481,7 +3660,7 @@ function CTLD:_GetVHFBeacon(Name)
table.insert(self.UsedVHFFrequencies, VHF)
beacon.name = Name
beacon.frequency = VHF / 1000000
beacon.modulation = radio.modulation.FM
beacon.modulation = CTLD.RadioModulation.FM
return beacon
end
@@ -3522,7 +3701,12 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship
ctldzone.name = Name or "NONE"
ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType
ctldzone.hasbeacon = HasBeacon or false
if Type == CTLD.CargoZoneType.BEACON then
self.droppedbeaconref[ctldzone.name] = zone:GetCoordinate()
ctldzone.timestamp = timer.getTime()
end
if HasBeacon then
ctldzone.fmbeacon = self:_GetFMBeacon(Name)
ctldzone.uhfbeacon = self:_GetUHFBeacon(Name)
@@ -3611,7 +3795,8 @@ function CTLD:CheckDroppedBeacons()
for _,_beacon in pairs (self.droppedBeacons) do
local beacon = _beacon -- #CTLD.CargoZone
local T0 = beacon.timestamp
if not beacon.timestamp then beacon.timestamp = timer.getTime() + timeout end
local T0 = beacon.timestamp
if timer.getTime() - T0 > timeout then
local name = beacon.name
self.droppedbeaconref[name] = nil
@@ -3682,22 +3867,53 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped)
end
end
local Sound = Sound or "beacon.ogg"
if IsDropped and Zone then
if Zone then
if IsDropped then
local ZoneCoord = Zone
local ZoneVec3 = ZoneCoord:GetVec3()
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
local Frequency = Mhz * 1000000 -- Freq in Hertz
local Sound = "l10n/DEFAULT/"..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight
elseif Zone then
local Sound = self.RadioPath..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straight
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
else
local ZoneCoord = Zone:GetCoordinate()
local ZoneVec3 = ZoneCoord:GetVec3()
local Frequency = Mhz * 1000000 -- Freq in Hertz
local Sound = "l10n/DEFAULT/"..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight
local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0}
local Frequency = Mhz * 1000000 -- Freq in Hert
local Sound = self.RadioPath..Sound
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straightt
self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation))
end
else
self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name)
end
return self
end
--- Set folder path where the CTLD sound files are located **within you mission (miz) file**.
-- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission.
-- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used.
-- @param #CTLD self
-- @param #string FolderPath The path to the sound files, e.g. "CTLD_Soundfiles/".
-- @return #CTLD self
function CTLD:SetSoundfilesFolder( FolderPath )
self:T(self.lid .. " SetSoundfilesFolder")
-- Check that it ends with /
if FolderPath then
local lastchar = string.sub( FolderPath, -1 )
if lastchar ~= "/" then
FolderPath = FolderPath .. "/"
end
end
-- Folderpath.
self.RadioPath = FolderPath
-- Info message.
self:I( self.lid .. string.format( "Setting sound files folder to: %s", self.RadioPath ) )
return self
end
--- (Internal) Function to refresh radio beacons
-- @param #CTLD self
function CTLD:_RefreshRadioBeacons()
@@ -3720,10 +3936,10 @@ function CTLD:_RefreshRadioBeacons()
local Name = czone.name
local FM = FMbeacon.frequency -- MHz
local VHF = VHFbeacon.frequency -- KHz
local UHF = UHFbeacon.frequency -- MHz
self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip, IsDropped)
local UHF = UHFbeacon.frequency -- MHz
self:_AddRadioBeacon(Name,Sound,FM, CTLD.RadioModulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped)
self:_AddRadioBeacon(Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped)
end
end
end
@@ -4235,6 +4451,8 @@ end
-- @param #CTLD self
-- @param Core.Zone#ZONE Zone The zone where to drop the troops.
-- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn.
-- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type!
-- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another!
-- @return #CTLD self
-- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone:
-- -- create a matching #CTLD_CARGO type
@@ -4242,8 +4460,8 @@ end
-- -- get a #ZONE object
-- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE
-- -- and go:
-- my_ctld:InjectTroops(dropzone,InjectTroopsType)
function CTLD:InjectTroops(Zone,Cargo)
-- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND})
function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation)
self:T(self.lid.." InjectTroops")
local cargo = Cargo -- #CTLD_CARGO
@@ -4275,8 +4493,10 @@ end
local temptable = cargo:GetTemplates() or {}
local factor = 1.5
local zone = Zone
local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2()
local randomcoord = zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2()
if PreciseLocation then
randomcoord = zone:GetCoordinate():GetVec2()
end
for _,_template in pairs(temptable) do
self.TroopCounter = self.TroopCounter + 1
local alias = string.format("%s-%d", _template, math.random(1,100000))
@@ -4611,7 +4831,7 @@ end
-- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build.
-- @return #CTLD self
function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle)
self:I({From, Event, To})
self:T({From, Event, To})
if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then
local playername = Unit:GetPlayerName()
local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0)
@@ -4737,7 +4957,7 @@ end
for _,_cargo in pairs (stcstable) do
local cargo = _cargo -- #CTLD_CARGO
local object = cargo:GetPositionable() -- Wrapper.Static#STATIC
if object and object:IsAlive() and cargo:WasDropped() then
if object and object:IsAlive() and (cargo:WasDropped() or not cargo:HasMoved()) then
statics[#statics+1] = cargo
end
end
@@ -4978,7 +5198,7 @@ end
self:InjectVehicles(dropzone,injectvehicle)
elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then
local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass)
self:InjectTroops(dropzone,injecttroops)
self:InjectTroops(dropzone,injecttroops,self.surfacetypes)
end
elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then
local cargotemplates = dataset[6]