mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Merge branch 'develop' into FF/Ops
This commit is contained in:
commit
3834991e6a
@ -3940,11 +3940,7 @@ do
|
||||
--
|
||||
-- # Demo Missions
|
||||
--
|
||||
-- ### [AI\_A2A\_GCICAP for Caucasus](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-200%20-%20AI_A2A%20-%20GCICAP%20Demonstration)
|
||||
-- ### [AI\_A2A\_GCICAP for NTTR](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-210%20-%20NTTR%20AI_A2A_GCICAP%20Demonstration)
|
||||
-- ### [AI\_A2A\_GCICAP for Normandy](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-220%20-%20NORMANDY%20AI_A2A_GCICAP%20Demonstration)
|
||||
--
|
||||
-- ### [AI\_A2A\_GCICAP for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching)
|
||||
-- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching/AID-A2A%20-%20AI%20A2A%20Dispatching)
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
|
||||
@ -1224,7 +1224,7 @@ function EVENT:onEvent( Event )
|
||||
if Event.TgtObjectCategory == Object.Category.STATIC then
|
||||
-- get base data
|
||||
Event.TgtDCSUnit = Event.target
|
||||
if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object
|
||||
if Event.target:isExist() and Event.id ~= 33 and not Event.TgtObjectCategory == Object.Category.COORDINATE then -- leave out ejected seat object
|
||||
Event.TgtDCSUnitName = Event.TgtDCSUnit:getName()
|
||||
Event.TgtUnitName = Event.TgtDCSUnitName
|
||||
Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false )
|
||||
|
||||
@ -186,7 +186,7 @@ do -- SET_BASE
|
||||
return Names
|
||||
end
|
||||
|
||||
--- Return a table of the Objects in the Set.
|
||||
--- Returns a table of the Objects in the Set.
|
||||
-- @param #SET_BASE self
|
||||
-- @return #table Table of objects.
|
||||
function SET_BASE:GetSetObjects() -- R2.3
|
||||
@ -388,7 +388,6 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @return Core.Base#BASE
|
||||
function SET_BASE:GetFirst()
|
||||
|
||||
local ObjectName = self.Index[1]
|
||||
local FirstObject = self.Set[ObjectName]
|
||||
self:T3( { FirstObject } )
|
||||
@ -399,8 +398,8 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @return Core.Base#BASE
|
||||
function SET_BASE:GetLast()
|
||||
|
||||
local ObjectName = self.Index[#self.Index]
|
||||
local tablemax = table.maxn(self.Index)
|
||||
local ObjectName = self.Index[tablemax]
|
||||
local LastObject = self.Set[ObjectName]
|
||||
self:T3( { LastObject } )
|
||||
return LastObject
|
||||
@ -410,8 +409,8 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @return Core.Base#BASE
|
||||
function SET_BASE:GetRandom()
|
||||
|
||||
local RandomItem = self.Set[self.Index[math.random( #self.Index )]]
|
||||
local tablemax = table.maxn(self.Index)
|
||||
local RandomItem = self.Set[self.Index[math.random(1,tablemax)]]
|
||||
self:T3( { RandomItem } )
|
||||
return RandomItem
|
||||
end
|
||||
@ -420,8 +419,7 @@ do -- SET_BASE
|
||||
-- @param #SET_BASE self
|
||||
-- @return #number Count
|
||||
function SET_BASE:Count()
|
||||
|
||||
return self.Index and #self.Index or 0
|
||||
return self.Index and table.maxn(self.Index) or 0
|
||||
end
|
||||
|
||||
--- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set).
|
||||
@ -2007,12 +2005,7 @@ do -- SET_UNIT
|
||||
-- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT.
|
||||
-- * @{#SET_UNIT.ForEachUnitInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence completely in a @{Core.Zone}, providing the UNIT object and optional parameters to the called function.
|
||||
-- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence not in a @{Core.Zone}, providing the UNIT object and optional parameters to the called function.
|
||||
--
|
||||
-- Planned iterators methods in development are (so these are not yet available):
|
||||
--
|
||||
-- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT.
|
||||
-- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Core.Zone}, providing the UNIT and optional parameters to the called function.
|
||||
-- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Core.Zone}, providing the UNIT and optional parameters to the called function.
|
||||
-- * @{#SET_UNIT:ForEachUnitPerThreatLevel}: Iterate the SET_UNIT **sorted *per Threat Level** and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters
|
||||
--
|
||||
-- ## 5) SET_UNIT atomic methods
|
||||
--
|
||||
@ -3865,6 +3858,8 @@ do -- SET_CLIENT
|
||||
Countries = nil,
|
||||
ClientPrefixes = nil,
|
||||
Zones = nil,
|
||||
Playernames = nil,
|
||||
Callsigns = nil,
|
||||
},
|
||||
FilterMeta = {
|
||||
Coalitions = {
|
||||
@ -3937,6 +3932,40 @@ do -- SET_CLIENT
|
||||
return ClientFound
|
||||
end
|
||||
|
||||
--- Builds a set of clients of certain callsigns.
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #string Callsigns Can be a single string e.g. "Ford", or a table of strings e.g. {"Uzi","Enfield","Chevy"}. Refers to the callsigns as they can be set in the mission editor.
|
||||
-- @return #SET_CLIENT self
|
||||
function SET_CLIENT:FilterCallsigns( Callsigns )
|
||||
if not self.Filter.Callsigns then
|
||||
self.Filter.Callsigns = {}
|
||||
end
|
||||
if type( Callsigns ) ~= "table" then
|
||||
Callsigns = { Callsigns }
|
||||
end
|
||||
for callsignID, callsign in pairs( Callsigns ) do
|
||||
self.Filter.Callsigns[callsign] = callsign
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of clients of certain playernames.
|
||||
-- @param #SET_CLIENT self
|
||||
-- @param #string Playernames Can be a single string e.g. "Apple", or a table of strings e.g. {"Walter","Hermann","Gonzo"}. Useful if you have e.g. a common squadron prefix.
|
||||
-- @return #SET_CLIENT self
|
||||
function SET_CLIENT:FilterPlayernames( Playernames )
|
||||
if not self.Filter.Playernames then
|
||||
self.Filter.Playernames = {}
|
||||
end
|
||||
if type( Playernames ) ~= "table" then
|
||||
Playernames = { Playernames }
|
||||
end
|
||||
for PlayernameID, playername in pairs( Playernames ) do
|
||||
self.Filter.Playernames[playername] = playername
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- Builds a set of clients of coalitions.
|
||||
-- Possible current coalitions are red, blue and neutral.
|
||||
-- @param #SET_CLIENT self
|
||||
@ -4224,9 +4253,10 @@ do -- SET_CLIENT
|
||||
|
||||
if self.Filter.Active ~= nil then
|
||||
local MClientActive = false
|
||||
if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true) then
|
||||
if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true and MClient:IsAlive() == true) then
|
||||
MClientActive = true
|
||||
end
|
||||
--self:I( { "Evaluated Active", MClientActive } )
|
||||
MClientInclude = MClientInclude and MClientActive
|
||||
end
|
||||
|
||||
@ -4292,20 +4322,46 @@ do -- SET_CLIENT
|
||||
self:T( { "Evaluated Prefix", MClientPrefix } )
|
||||
MClientInclude = MClientInclude and MClientPrefix
|
||||
end
|
||||
end
|
||||
|
||||
if self.Filter.Zones then
|
||||
local MClientZone = false
|
||||
for ZoneName, Zone in pairs( self.Filter.Zones ) do
|
||||
self:T3( "Zone:", ZoneName )
|
||||
local unit = MClient:GetClientGroupUnit()
|
||||
if unit and unit:IsInZone(Zone) then
|
||||
MClientZone = true
|
||||
end
|
||||
self:T3( "Zone:", ZoneName )
|
||||
local unit = MClient:GetClientGroupUnit()
|
||||
if unit and unit:IsInZone(Zone) then
|
||||
MClientZone = true
|
||||
end
|
||||
end
|
||||
MClientInclude = MClientInclude and MClientZone
|
||||
end
|
||||
|
||||
if self.Filter.Playernames then
|
||||
local MClientPlayername = false
|
||||
local playername = MClient:GetPlayerName() or "Unknown"
|
||||
--self:I(playername)
|
||||
for _,_Playername in pairs(self.Filter.Playernames) do
|
||||
if playername and string.find(playername,_Playername) then
|
||||
MClientPlayername = true
|
||||
end
|
||||
end
|
||||
self:T( { "Evaluated Playername", MClientPlayername } )
|
||||
MClientInclude = MClientInclude and MClientPlayername
|
||||
end
|
||||
|
||||
if self.Filter.Callsigns then
|
||||
local MClientCallsigns = false
|
||||
local callsign = MClient:GetCallsign()
|
||||
--self:I(callsign)
|
||||
for _,_Callsign in pairs(self.Filter.Callsigns) do
|
||||
if callsign and string.find(callsign,_Callsign) then
|
||||
MClientCallsigns = true
|
||||
end
|
||||
end
|
||||
self:T( { "Evaluated Callsign", MClientCallsigns } )
|
||||
MClientInclude = MClientInclude and MClientCallsigns
|
||||
end
|
||||
|
||||
end
|
||||
self:T2( MClientInclude )
|
||||
return MClientInclude
|
||||
end
|
||||
@ -7334,7 +7390,7 @@ do -- SET_SCENERY
|
||||
|
||||
if ZoneSet then
|
||||
for _,_zone in pairs(ZoneSet.Set) do
|
||||
--self:I("Zone type handed: "..tostring(_zone.ClassName))
|
||||
self:T("Zone type handed: "..tostring(_zone.ClassName))
|
||||
table.insert(zonenames,_zone:GetName())
|
||||
end
|
||||
self:AddSceneryByName(zonenames)
|
||||
@ -7348,7 +7404,7 @@ do -- SET_SCENERY
|
||||
-- @param Core.Zone#ZONE Zone The zone to be scanned. Can be a ZONE_RADIUS (round) or a ZONE_POLYGON (e.g. Quad-Point)
|
||||
-- @return #SET_SCENERY
|
||||
function SET_SCENERY:NewFromZone(Zone)
|
||||
local zone = Zone -- Core.Zone#ZONE_POLYGON
|
||||
local zone = Zone -- Core.Zone#ZONE_RADIUS
|
||||
if type(Zone) == "string" then
|
||||
zone = ZONE:FindByName(Zone)
|
||||
end
|
||||
@ -7468,7 +7524,29 @@ do -- SET_SCENERY
|
||||
|
||||
return CountU
|
||||
end
|
||||
|
||||
--- Get a table of alive objects.
|
||||
-- @param #SET_GROUP self
|
||||
-- @return #table Table of alive objects
|
||||
-- @return Core.Set#SET_SCENERY SET of alive objects
|
||||
function SET_SCENERY:GetAliveSet()
|
||||
self:F2()
|
||||
|
||||
local AliveSet = SET_SCENERY:New()
|
||||
|
||||
-- Clean the Set before returning with only the alive Groups.
|
||||
for GroupName, GroupObject in pairs( self.Set ) do
|
||||
local GroupObject = GroupObject -- Wrapper.Group#GROUP
|
||||
if GroupObject then
|
||||
if GroupObject:IsAlive() then
|
||||
AliveSet:Add( GroupName, GroupObject )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return AliveSet.Set or {}, AliveSet
|
||||
end
|
||||
|
||||
--- Iterate the SET_SCENERY and call an iterator function for each **alive** SCENERY, providing the SCENERY and optional parameters.
|
||||
-- @param #SET_SCENERY self
|
||||
-- @param #function IteratorFunction The function that will be called when there is an alive SCENERY in the SET_SCENERY. The function needs to accept a SCENERY parameter.
|
||||
|
||||
@ -94,6 +94,7 @@
|
||||
-- @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
|
||||
@ -309,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 = {
|
||||
@ -351,6 +365,7 @@ ATIS = {
|
||||
relHumidity = nil,
|
||||
ReportmBar = false,
|
||||
TransmitOnlyWithPlayers = false,
|
||||
ATISforFARPs = false,
|
||||
}
|
||||
|
||||
--- NATO alphabet.
|
||||
@ -593,7 +608,7 @@ _ATIS = {}
|
||||
|
||||
--- ATIS class version.
|
||||
-- @field #string version
|
||||
ATIS.version = "0.9.12"
|
||||
ATIS.version = "0.9.14"
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO list
|
||||
@ -1049,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.
|
||||
@ -1257,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 ) )
|
||||
|
||||
@ -1473,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 ---
|
||||
------------
|
||||
@ -1790,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
|
||||
@ -1865,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
|
||||
@ -1884,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
|
||||
@ -1987,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
|
||||
@ -2019,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
|
||||
@ -2050,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.
|
||||
@ -2117,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
|
||||
|
||||
@ -2246,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
|
||||
@ -2263,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
|
||||
@ -2399,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, ";", " . " )
|
||||
|
||||
@ -1317,6 +1317,62 @@ function AUFTRAG:NewCAP(ZoneCAP, Altitude, Speed, Coordinate, Heading, Leg, Targ
|
||||
return mission
|
||||
end
|
||||
|
||||
--- **[AIR]** Create a CAP mission on a group.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Wrapper.Group#GROUP Grp.
|
||||
-- @param #number Altitude Orbit altitude in feet. Default is 6,000 ft.
|
||||
-- @param #number Speed Orbit speed in knots. Default 250 KIAS.
|
||||
-- @param #number RelHeading Relative heading [0, 360) of race-track pattern in degrees wrt heading of the carrier. Default is heading of the carrier.
|
||||
-- @param #number Leg Length of race-track in NM. Default 14 NM.
|
||||
-- @param #number OffsetDist Relative distance of the first race-track point wrt to the carrier. Default 6 NM.
|
||||
-- @param #number OffsetAngle Relative angle of the first race-track point wrt. to the carrier. Default 180 (behind the boat).
|
||||
-- @param #number UpdateDistance Threshold distance in NM before orbit pattern is updated. Default 5 NM.
|
||||
-- @param #table TargetTypes (Optional) Table of target types. Default `{"Helicopters", "Ground Units", "Light armed ships"}`.
|
||||
-- @param #number EngageRange Max range in nautical miles that the escort group(s) will engage enemies. Default 32 NM (60 km).
|
||||
-- @return #AUFTRAG self
|
||||
function AUFTRAG:NewCAPGROUP(Grp, Altitude, Speed, RelHeading, Leg, OffsetDist, OffsetAngle, UpdateDistance, TargetTypes, EngageRange)
|
||||
|
||||
-- Ensure given TargetTypes parameter is a table.
|
||||
if TargetTypes then
|
||||
if type(TargetTypes)~="table" then
|
||||
TargetTypes={TargetTypes}
|
||||
end
|
||||
end
|
||||
-- Six NM astern.
|
||||
local OffsetVec2={r=OffsetDist or 6, phi=OffsetAngle or 180}
|
||||
|
||||
-- Default leg.
|
||||
Leg=Leg or 14
|
||||
|
||||
local Heading=nil
|
||||
if RelHeading then
|
||||
Heading=-math.abs(RelHeading)
|
||||
end
|
||||
|
||||
-- Create orbit mission.
|
||||
local mission=AUFTRAG:NewORBIT_GROUP(Grp, Altitude, Speed, Leg, Heading, OffsetVec2, UpdateDistance)
|
||||
-- Mission type CAP.
|
||||
mission.type=AUFTRAG.Type.CAP
|
||||
mission:_SetLogID()
|
||||
|
||||
-- DCS task parameters:
|
||||
local engage = EngageRange or 32
|
||||
local zoneCAPGroup = ZONE_GROUP:New("CAPGroup", Grp, UTILS.NMToMeters(engage))
|
||||
mission.engageZone=zoneCAPGroup
|
||||
mission.engageTargetTypes=TargetTypes or {"Air"}
|
||||
|
||||
-- Mission options:
|
||||
mission.missionTask=ENUMS.MissionTask.CAP
|
||||
mission.optionROE=ENUMS.ROE.OpenFire
|
||||
mission.optionROT=ENUMS.ROT.EvadeFire
|
||||
|
||||
mission.categories={AUFTRAG.Category.AIRCRAFT}
|
||||
|
||||
mission.DCStask=mission:GetDCSMissionTask()
|
||||
|
||||
return mission
|
||||
end
|
||||
|
||||
--- **[AIR]** Create a CAS mission.
|
||||
-- @param #AUFTRAG self
|
||||
-- @param Core.Zone#ZONE_RADIUS ZoneCAS Circular CAS zone. Detected targets in this zone will be engaged.
|
||||
@ -6112,20 +6168,15 @@ function AUFTRAG:GetDCSMissionTask()
|
||||
-- Race-track vector.
|
||||
orbitRaceTrack=UTILS.Vec2Translate(orbitVec2, self.orbitLeg, heading)
|
||||
end
|
||||
|
||||
-- Debug
|
||||
--UTILS.RemoveMark(self.orbitCenterMarkID)
|
||||
--self.orbitCenterMarkID=COORDINATE:NewFromVec2(orbitVec2):MarkToAll("Orbit Center")
|
||||
|
||||
-- Debug show arrow.
|
||||
--if orbitRaceTrack then
|
||||
--UTILS.RemoveMark(self.orbitArrowMarkID)
|
||||
--self.orbitArrowMarkID=COORDINATE:NewFromVec2(orbitVec2):ArrowToAll(COORDINATE:NewFromVec2(orbitRaceTrack))
|
||||
--end
|
||||
|
||||
local orbitRaceTrackCoord = nil
|
||||
if orbitRaceTrack then
|
||||
orbitRaceTrackCoord = COORDINATE:NewFromVec2(orbitRaceTrack)
|
||||
end
|
||||
|
||||
-- Create orbit task.
|
||||
local DCStask=CONTROLLABLE.TaskOrbit(nil, COORDINATE:NewFromVec2(orbitVec2), self.orbitAltitude, self.orbitSpeed, orbitRaceTrack)
|
||||
|
||||
local DCStask=CONTROLLABLE.TaskOrbit(nil, COORDINATE:NewFromVec2(orbitVec2), self.orbitAltitude, self.orbitSpeed, orbitRaceTrackCoord)
|
||||
|
||||
-- Add DCS task.
|
||||
table.insert(DCStasks, DCStask)
|
||||
|
||||
|
||||
@ -2,9 +2,7 @@
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
-- ## AWACS
|
||||
--
|
||||
-- * MOOSE AI AWACS Operations using text-to-speech.
|
||||
-- **AWACS** - MOOSE AI AWACS Operations using text-to-speech.
|
||||
--
|
||||
-- ===
|
||||
--
|
||||
@ -19,7 +17,7 @@
|
||||
-- ===
|
||||
--
|
||||
-- ### Author: **applevangelist**
|
||||
-- @date Last Update November 2022
|
||||
-- @date Last Update December 2022
|
||||
-- @module Ops.AWACS
|
||||
-- @image OPS_AWACS.jpg
|
||||
|
||||
@ -106,7 +104,7 @@ do
|
||||
-- @field #boolean NoGroupTags Set to true if you don't want group tags.
|
||||
-- @field #boolean SuppressScreenOutput Set to true to suppress all screen output.
|
||||
-- @field #boolean NoMissileCalls Suppress missile callouts
|
||||
-- @field #boolean PlayerCapAssigment Assign players to CAP tasks when they are logged on
|
||||
-- @field #boolean PlayerCapAssignment Assign players to CAP tasks when they are logged on
|
||||
-- @field #number GoogleTTSPadding
|
||||
-- @field #number WindowsTTSPadding
|
||||
-- @field #boolean AllowMarkers
|
||||
@ -114,6 +112,7 @@ do
|
||||
-- @field #boolean GCI Act as GCI
|
||||
-- @field Wrapper.Group#GROUP GCIGroup EWR group object for GCI ops
|
||||
-- @field #string locale Localization
|
||||
-- @field #boolean IncludeHelicopters
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@ -358,7 +357,7 @@ do
|
||||
-- testawacs.maxassigndistance = 100 -- Don't assign targets further out than this, in NM.
|
||||
-- testawacs.debug = false -- set to true to produce more log output.
|
||||
-- testawacs.NoMissileCalls = true -- suppress missile callouts
|
||||
-- testawacs.PlayerCapAssigment = true -- no intercept task assignments for players
|
||||
-- testawacs.PlayerCapAssignment = true -- no intercept task assignments for players
|
||||
-- testawacs.invisible = false -- set AWACS to be invisible to hostiles
|
||||
-- testawacs.immortal = false -- set AWACS to be immortal
|
||||
-- -- By default, the radio queue is checked every 10 secs. This is altered by the calculated length of the sentence to speak
|
||||
@ -367,6 +366,7 @@ do
|
||||
-- testawacs.GoogleTTSPadding = 1 -- seconds
|
||||
-- testawacs.WindowsTTSPadding = 2.5 -- seconds
|
||||
-- testawacs.PikesSpecialSwitch = false -- if set to true, AWACS will omit the "doing xy knots" on the station assignement callout
|
||||
-- testawacs.IncludeHelicopters = false -- if set to true, Helicopter pilots will also get the AWACS Menu and options
|
||||
--
|
||||
-- ## 9.2 Bespoke random voices for AI CAP (Google TTS only)
|
||||
--
|
||||
@ -499,7 +499,7 @@ do
|
||||
-- @field #AWACS
|
||||
AWACS = {
|
||||
ClassName = "AWACS", -- #string
|
||||
version = "0.2.49", -- #string
|
||||
version = "0.2.51", -- #string
|
||||
lid = "", -- #string
|
||||
coalition = coalition.side.BLUE, -- #number
|
||||
coalitiontxt = "blue", -- #string
|
||||
@ -580,12 +580,13 @@ AWACS = {
|
||||
NoMissileCalls = true,
|
||||
GoogleTTSPadding = 1,
|
||||
WindowsTTSPadding = 2.5,
|
||||
PlayerCapAssigment = true,
|
||||
PlayerCapAssignment = true,
|
||||
AllowMarkers = false,
|
||||
PlayerStationName = nil,
|
||||
GCI = false,
|
||||
GCIGroup = nil,
|
||||
locale = "en",
|
||||
IncludeHelicopters = false,
|
||||
}
|
||||
|
||||
---
|
||||
@ -916,13 +917,13 @@ AWACS.TaskStatus = {
|
||||
--@field #boolean FromAI
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- TODO-List 0.2.42
|
||||
-- TODO-List 0.2.51
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
--
|
||||
-- DONE - WIP - Player tasking, VID
|
||||
-- DONE - Localization (sensible?)
|
||||
-- TODO - (LOW) LotATC
|
||||
-- TODO - SW Optimization
|
||||
-- DONE - SW Optimization
|
||||
-- WONTDO - Maybe check in AI only when airborne
|
||||
-- DONE - remove SSML tag when not on google (currently sometimes spoken)
|
||||
-- DONE - Maybe - Assign specific number of AI CAP to a station
|
||||
@ -953,6 +954,7 @@ AWACS.TaskStatus = {
|
||||
-- DONE - Shift Length AWACS/AI
|
||||
-- DONE - (WIP) Reporting
|
||||
-- DONE - Do not report non-airborne groups
|
||||
-- DONE - Added option for helos
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-- Constructor
|
||||
@ -1125,7 +1127,7 @@ function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,Station
|
||||
self.MenuStrict = true
|
||||
self.maxassigndistance = 100 --nm
|
||||
self.NoMissileCalls = true
|
||||
self.PlayerCapAssigment = true
|
||||
self.PlayerCapAssignment = true
|
||||
|
||||
-- managed groups
|
||||
self.ManagedGrps = {} -- #table of #AWACS.ManagedGroup entries
|
||||
@ -3470,7 +3472,7 @@ function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr)
|
||||
local CAPVoice = self.CAPVoice
|
||||
|
||||
if self.PathToGoogleKey then
|
||||
CAPVoice = AWACS.CapVoices[math.floor(math.random(1,10))]
|
||||
CAPVoice = self.CapVoices[math.floor(math.random(1,10))]
|
||||
end
|
||||
|
||||
FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,CAPVoice,self.Port,self.PathToGoogleKey,"FLIGHT")
|
||||
@ -3589,13 +3591,15 @@ function AWACS:_SetClientMenus()
|
||||
local bogeydope = MENU_GROUP_COMMAND:New(cgrp,"Bogey Dope",basemenu,self._BogeyDope,self,cgrp)
|
||||
local picture = MENU_GROUP_COMMAND:New(cgrp,"Picture",basemenu,self._Picture,self,cgrp)
|
||||
local declare = MENU_GROUP_COMMAND:New(cgrp,"Declare",basemenu,self._Declare,self,cgrp)
|
||||
|
||||
local tasking = MENU_GROUP:New(cgrp,"Tasking",basemenu)
|
||||
local showtask = MENU_GROUP_COMMAND:New(cgrp,"Showtask",tasking,self._Showtask,self,cgrp)
|
||||
local commit = MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp)
|
||||
local unable = MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp)
|
||||
local abort = MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp)
|
||||
--local judy = MENU_GROUP_COMMAND:New(cgrp,"Judy",tasking,self._Judy,self,cgrp)
|
||||
|
||||
if self.PlayerCapAssignment then
|
||||
local commit = MENU_GROUP_COMMAND:New(cgrp,"Commit",tasking,self._Commit,self,cgrp)
|
||||
local unable = MENU_GROUP_COMMAND:New(cgrp,"Unable",tasking,self._Unable,self,cgrp)
|
||||
local abort = MENU_GROUP_COMMAND:New(cgrp,"Abort",tasking,self._TaskAbort,self,cgrp)
|
||||
--local judy = MENU_GROUP_COMMAND:New(cgrp,"Judy",tasking,self._Judy,self,cgrp)
|
||||
end
|
||||
|
||||
if self.AwacsROE == AWACS.ROE.POLICE or self.AwacsROE == AWACS.ROE.VID then
|
||||
local vid = MENU_GROUP:New(cgrp,"VID as",tasking)
|
||||
@ -5534,6 +5538,20 @@ end
|
||||
-- FSM Functions
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
--- [Internal] onbeforeStart
|
||||
-- @param #AWACS self
|
||||
-- @param #string From
|
||||
-- @param #string Event
|
||||
-- @param #string To
|
||||
-- @return #AWACS self
|
||||
function AWACS:onbeforeStart(From,Event,to)
|
||||
self:T({From, Event, To})
|
||||
if self.IncludeHelicopters then
|
||||
self.clientset:FilterCategories("helicopter")
|
||||
end
|
||||
return self
|
||||
end
|
||||
|
||||
--- [Internal] onafterStart
|
||||
-- @param #AWACS self
|
||||
-- @param #string From
|
||||
@ -5959,7 +5977,7 @@ function AWACS:onafterStatus(From, Event, To)
|
||||
local AI, Humans = self:_GetIdlePilots()
|
||||
-- assign Pilot if there are targets and available Pilots, prefer Humans to AI
|
||||
-- DONE - Implemented AI First, Humans laters - need to work out how to loop the targets to assign a pilot
|
||||
if outcome and #Humans > 0 and self.PlayerCapAssigment then
|
||||
if outcome and #Humans > 0 and self.PlayerCapAssignment then
|
||||
-- add a task for AI
|
||||
self:_AssignPilotToTarget(Humans,targets)
|
||||
end
|
||||
|
||||
@ -711,6 +711,7 @@ 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`
|
||||
--
|
||||
-- ## 2.1 User functions
|
||||
--
|
||||
@ -1015,7 +1016,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
|
||||
@ -1078,7 +1088,7 @@ CTLD.UnitTypes = {
|
||||
|
||||
--- CTLD class version.
|
||||
-- @field #string version
|
||||
CTLD.version="1.0.20"
|
||||
CTLD.version="1.0.24"
|
||||
|
||||
--- Instantiate a new CTLD.
|
||||
-- @param #CTLD self
|
||||
@ -1161,6 +1171,7 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
|
||||
-- radio beacons
|
||||
self.RadioSound = "beacon.ogg"
|
||||
self.RadioPath = "l10n/DEFAULT/"
|
||||
|
||||
-- zones stuff
|
||||
self.pickupZones = {}
|
||||
@ -1243,6 +1254,9 @@ function CTLD:New(Coalition, Prefixes, Alias)
|
||||
self.usesubcats = false
|
||||
self.subcats = {}
|
||||
|
||||
-- disallow building in loadzones
|
||||
self.nobuildinloadzones = true
|
||||
|
||||
local AliaS = string.gsub(self.alias," ","_")
|
||||
self.filename = string.format("CTLD_%s_Persist.csv",AliaS)
|
||||
|
||||
@ -1635,12 +1649,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 +1704,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 +1712,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 +2150,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 +2882,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
|
||||
@ -3440,7 +3506,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 +3526,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 +3547,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
|
||||
|
||||
@ -3690,24 +3756,45 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped)
|
||||
local Sound = Sound or "beacon.ogg"
|
||||
if IsDropped and Zone then
|
||||
local ZoneCoord = Zone
|
||||
local ZoneVec3 = ZoneCoord:GetVec3()
|
||||
local ZoneVec3 = ZoneCoord:GetVec3(1)
|
||||
local Frequency = string.format("%09d",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 status = string.format("***** Beacon added Freq %s Mod %s", Mhz, UTILS.GetModulationName(Modulation))
|
||||
--MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug)
|
||||
local Sound = self.RadioPath..Sound
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight
|
||||
elseif Zone then
|
||||
local ZoneCoord = Zone:GetCoordinate(2)
|
||||
local ZoneCoord = Zone:GetCoordinate(1)
|
||||
local ZoneVec3 = ZoneCoord:GetVec3()
|
||||
local Frequency = string.format("%09d",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 status = string.format("***** Beacon added Freq %s Mod %s", Mhz, UTILS.GetModulationName(Modulation))
|
||||
--MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug)
|
||||
local Sound = self.RadioPath..Sound
|
||||
trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight
|
||||
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()
|
||||
@ -3730,10 +3817,14 @@ 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
|
||||
-- local co = coroutine.create(self._AddRadioBeacon)
|
||||
--coroutine.resume(co, self, Name,Sound,FM,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
--coroutine.resume(co, self, Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped)
|
||||
--coroutine.resume(co, self, Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped)
|
||||
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
|
||||
@ -4747,7 +4838,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
|
||||
|
||||
@ -712,7 +712,7 @@ end
|
||||
-- @param #FLIGHTGROUP self
|
||||
-- @return #boolean If true, has landed somewhere.
|
||||
function FLIGHTGROUP:IsLandedAt()
|
||||
is=self:Is("LandedAt")
|
||||
local is=self:Is("LandedAt")
|
||||
return is
|
||||
end
|
||||
|
||||
@ -908,7 +908,7 @@ function FLIGHTGROUP:Status()
|
||||
if mission and mission.updateDCSTask then
|
||||
|
||||
-- Orbit missions might need updates.
|
||||
if (mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER) and mission.orbitVec2 then
|
||||
if (mission:GetType()==AUFTRAG.Type.ORBIT or mission:GetType()==AUFTRAG.Type.RECOVERYTANKER or mission:GetType()==AUFTRAG.Type.CAP) and mission.orbitVec2 then
|
||||
|
||||
-- Get 2D vector of orbit target.
|
||||
local vec2=mission:GetTargetVec2()
|
||||
|
||||
@ -366,7 +366,10 @@ function PLAYERRECCE:SetReferencePoint(Coordinate,Name)
|
||||
if self.RPMarker then
|
||||
self.RPMarker:Remove()
|
||||
end
|
||||
local text = string.format("%s RP %s\n%s\n%s\n%s",self.Name,Name,Coordinate:ToStringLLDDM(),Coordinate:ToStringLLDMS(),Coordinate:ToStringMGRS())
|
||||
local llddm = Coordinate:ToStringLLDDM()
|
||||
local lldms = Coordinate:ToStringLLDMS()
|
||||
local mgrs = Coordinate:ToStringMGRS()
|
||||
local text = string.format("%s RP %s\n%s\n%s\n%s",self.Name,Name,llddm,lldms,mgrs)
|
||||
self.RPMarker = MARKER:New(Coordinate,text)
|
||||
self.RPMarker:ReadOnly()
|
||||
self.RPMarker:ToCoalition(self.Coalition)
|
||||
|
||||
@ -57,6 +57,7 @@ do
|
||||
-- @field #table NextTaskSuccess
|
||||
-- @field #table NextTaskFailure
|
||||
-- @field #string FinalState
|
||||
-- @field #string TypeName
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
|
||||
@ -95,7 +96,7 @@ PLAYERTASK = {
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASK.version="0.1.10"
|
||||
PLAYERTASK.version="0.1.11"
|
||||
|
||||
--- Generic task condition.
|
||||
-- @type PLAYERTASK.Condition
|
||||
@ -284,6 +285,14 @@ function PLAYERTASK:GetCoalition()
|
||||
return self.coalition
|
||||
end
|
||||
|
||||
--- [User] Get the Ops.Target#TARGET object for this task
|
||||
-- @param #PLAYERTASK self
|
||||
-- @return Ops.Target#TARGET Target
|
||||
function PLAYERTASK:GetTarget()
|
||||
self:T(self.lid.."GetTarget")
|
||||
return self.Target
|
||||
end
|
||||
|
||||
--- [USER] Add a free text description to this task.
|
||||
-- @param #PLAYERTASK self
|
||||
-- @param #string Text
|
||||
@ -770,7 +779,7 @@ function PLAYERTASK:onafterClientAdded(From, Event, To, Client)
|
||||
self:T({From, Event, To})
|
||||
if Client and self.verbose then
|
||||
local text = string.format("Player %s joined task %03d!",Client:GetPlayerName() or "Generic",self.PlayerTaskNr)
|
||||
self:I(self.lid..text)
|
||||
self:T(self.lid..text)
|
||||
end
|
||||
self.timestamp = timer.getAbsTime()
|
||||
return self
|
||||
@ -925,6 +934,8 @@ do
|
||||
-- @field #boolean ShowMagnetic Also show magnetic angles
|
||||
-- @field #boolean InfoHasCoordinate
|
||||
-- @field #boolean InfoHasLLDDM
|
||||
-- @field #table PlayerMenuTag
|
||||
-- @field #boolean UseTypeNames
|
||||
-- @extends Core.Fsm#FSM
|
||||
|
||||
---
|
||||
@ -1111,6 +1122,13 @@ do
|
||||
-- BRIEFING = "Briefing",
|
||||
-- TARGETLOCATION ="Target location",
|
||||
-- COORDINATE = "Coordinate",
|
||||
-- INFANTRY = "Infantry",
|
||||
-- TECHNICAL = "Technical",
|
||||
-- ARTILLERY = "Artillery",
|
||||
-- TANKS = "Tanks",
|
||||
-- AIRDEFENSE = "Airdefense",
|
||||
-- SAM = "SAM",
|
||||
-- GROUP = "Group",
|
||||
-- },
|
||||
--
|
||||
-- e.g.
|
||||
@ -1232,6 +1250,7 @@ PLAYERTASKCONTROLLER = {
|
||||
PlayerFlashMenu = {},
|
||||
PlayerJoinMenu = {},
|
||||
PlayerInfoMenu = {},
|
||||
PlayerMenuTag = {},
|
||||
noflaresmokemenu = false,
|
||||
TransmitOnlyWithPlayers = true,
|
||||
buddylasing = false,
|
||||
@ -1241,6 +1260,7 @@ PLAYERTASKCONTROLLER = {
|
||||
ShowMagnetic = true,
|
||||
InfoHasLLDDM = false,
|
||||
InfoHasCoordinate = false,
|
||||
UseTypeNames = false,
|
||||
}
|
||||
|
||||
---
|
||||
@ -1339,6 +1359,13 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
BRIEFING = "Briefing",
|
||||
TARGETLOCATION ="Target location",
|
||||
COORDINATE = "Coordinate",
|
||||
INFANTRY = "Infantry",
|
||||
TECHNICAL = "Technical",
|
||||
ARTILLERY = "Artillery",
|
||||
TANKS = "Tanks",
|
||||
AIRDEFENSE = "Airdefense",
|
||||
SAM = "SAM",
|
||||
GROUP = "Group",
|
||||
},
|
||||
DE = {
|
||||
TASKABORT = "Auftrag abgebrochen!",
|
||||
@ -1404,19 +1431,26 @@ PLAYERTASKCONTROLLER.Messages = {
|
||||
BRIEFING = "Briefing",
|
||||
TARGETLOCATION ="Zielposition",
|
||||
COORDINATE = "Koordinate",
|
||||
INFANTRY = "Infantrie",
|
||||
TECHNICAL = "Technische",
|
||||
ARTILLERY = "Artillerie",
|
||||
TANKS = "Panzer",
|
||||
AIRDEFENSE = "Flak",
|
||||
SAM = "Luftabwehr",
|
||||
GROUP = "Einheit",
|
||||
},
|
||||
}
|
||||
|
||||
--- PLAYERTASK class version.
|
||||
-- @field #string version
|
||||
PLAYERTASKCONTROLLER.version="0.1.51"
|
||||
PLAYERTASKCONTROLLER.version="0.1.54"
|
||||
|
||||
--- Create and run a new TASKCONTROLLER instance.
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #string Name Name of this controller
|
||||
-- @param #number Coalition of this controller, e.g. coalition.side.BLUE
|
||||
-- @param #string Type Type of the tasks controlled, defaults to PLAYERTASKCONTROLLER.Type.A2G
|
||||
-- @param #string ClientFilter (optional) Additional prefix filter for the SET_CLIENT
|
||||
-- @param #string ClientFilter (optional) Additional prefix filter for the SET_CLIENT. Can be handed as @{Core.Set#SET_CLIENT} also.
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
|
||||
@ -1467,10 +1501,20 @@ function PLAYERTASKCONTROLLER:New(Name, Coalition, Type, ClientFilter)
|
||||
self.noflaresmokemenu = false
|
||||
|
||||
self.ShowMagnetic = true
|
||||
|
||||
self.UseTypeNames = false
|
||||
|
||||
local IsClientSet = false
|
||||
|
||||
if ClientFilter and type(ClientFilter) == "table" and ClientFilter.ClassName and ClientFilter.ClassName == "SET_CLIENT" then
|
||||
-- we have a predefined SET_CLIENT
|
||||
self.ClientSet = ClientFilter
|
||||
IsClientSet = true
|
||||
end
|
||||
|
||||
if ClientFilter then
|
||||
if ClientFilter and not IsClientSet then
|
||||
self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterPrefixes(ClientFilter):FilterStart()
|
||||
else
|
||||
elseif not IsClientSet then
|
||||
self.ClientSet = SET_CLIENT:New():FilterCoalitions(string.lower(self.CoalitionName)):FilterActive(true):FilterStart()
|
||||
end
|
||||
|
||||
@ -1605,6 +1649,24 @@ function PLAYERTASKCONTROLLER:_InitLocalization()
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Show target menu entries of type names for GROUND targets (off by default!), e.g. "Tank Group..."
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:SetEnableUseTypeNames()
|
||||
self:T(self.lid.."SetEnableUseTypeNames")
|
||||
self.UseTypeNames = true
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Do not show target menu entries of type names for GROUND targets
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
function PLAYERTASKCONTROLLER:SetDisableUseTypeNames()
|
||||
self:T(self.lid.."SetDisableUseTypeNames")
|
||||
self.UseTypeNames = false
|
||||
return self
|
||||
end
|
||||
|
||||
--- [User] Set flash directions option for player (player based info)
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param #boolean OnOff Set to `true` to switch on and `false` to switch off. Default is OFF.
|
||||
@ -1961,6 +2023,9 @@ function PLAYERTASKCONTROLLER:_EventHandler(EventData)
|
||||
end
|
||||
elseif EventData.id == EVENTS.PlayerEnterAircraft and EventData.IniCoalition == self.Coalition then
|
||||
if EventData.IniPlayerName and EventData.IniGroup and self.UseSRS then
|
||||
if self.ClientSet:IsNotInSet(CLIENT:FindByName(EventData.IniUnitName)) then
|
||||
return self
|
||||
end
|
||||
self:T(self.lid.."Event for player: "..EventData.IniPlayerName)
|
||||
local frequency = self.Frequency
|
||||
local freqtext = ""
|
||||
@ -2099,7 +2164,7 @@ function PLAYERTASKCONTROLLER:_GetTasksPerType()
|
||||
self:T(self.lid.."_GetTasksPerType")
|
||||
local tasktypes = self:_GetAvailableTaskTypes()
|
||||
|
||||
self:T({tasktypes})
|
||||
--self:T({tasktypes})
|
||||
|
||||
-- Sort tasks per threat level first
|
||||
local datatable = self.TaskQueue:GetDataTable()
|
||||
@ -2132,7 +2197,7 @@ end
|
||||
function PLAYERTASKCONTROLLER:_CheckTargetQueue()
|
||||
self:T(self.lid.."_CheckTargetQueue")
|
||||
if self.TargetQueue:Count() > 0 then
|
||||
local object = self.TargetQueue:Pull()
|
||||
local object = self.TargetQueue:Pull() -- Wrapper.Positionable#POSITIONABLE
|
||||
local target = TARGET:New(object)
|
||||
if object.menuname then
|
||||
target.menuname = object.menuname
|
||||
@ -2140,6 +2205,38 @@ function PLAYERTASKCONTROLLER:_CheckTargetQueue()
|
||||
target.freetext = object.freetext
|
||||
end
|
||||
end
|
||||
|
||||
if self.UseTypeNames and object:IsGround() then
|
||||
-- * Threat level 0: Unit is unarmed.
|
||||
-- * Threat level 1: Unit is infantry.
|
||||
-- * Threat level 2: Unit is an infantry vehicle.
|
||||
-- * Threat level 3: Unit is ground artillery.
|
||||
-- * Threat level 4: Unit is a tank.
|
||||
-- * Threat level 5: Unit is a modern tank or ifv with ATGM.
|
||||
-- * Threat level 6: Unit is a AAA.
|
||||
-- * Threat level 7: Unit is a SAM or manpad, IR guided.
|
||||
-- * Threat level 8: Unit is a Short Range SAM, radar guided.
|
||||
-- * Threat level 9: Unit is a Medium Range SAM, radar guided.
|
||||
-- * Threat level 10: Unit is a Long Range SAM, radar guided.
|
||||
local threat = object:GetThreatLevel()
|
||||
local typekey = "INFANTRY"
|
||||
if threat == 0 or threat == 2 then
|
||||
typekey = "TECHNICAL"
|
||||
elseif threat == 3 then
|
||||
typekey = "ARTILLERY"
|
||||
elseif threat == 4 or threat == 5 then
|
||||
typekey = "TANKS"
|
||||
elseif threat == 6 or threat == 7 then
|
||||
typekey = "AIRDEFENSE"
|
||||
elseif threat >= 8 then
|
||||
typekey = "SAM"
|
||||
end
|
||||
local typename = self.gettext:GetEntry(typekey,self.locale)
|
||||
local gname = self.gettext:GetEntry("GROUP",self.locale)
|
||||
target.TypeName = string.format("%s %s",typename,gname)
|
||||
--self:T(self.lid.."Target TypeName = "..target.TypeName)
|
||||
end
|
||||
|
||||
self:_AddTask(target)
|
||||
end
|
||||
return self
|
||||
@ -2614,6 +2711,7 @@ function PLAYERTASKCONTROLLER:_AddTask(Target)
|
||||
end
|
||||
|
||||
task.coalition = self.Coalition
|
||||
task.TypeName = Target.TypeName
|
||||
|
||||
if type == AUFTRAG.Type.BOMBRUNWAY then
|
||||
-- task to handle event shot
|
||||
@ -2652,6 +2750,7 @@ end
|
||||
-- @param #PLAYERTASKCONTROLLER self
|
||||
-- @param Ops.PlayerTask#PLAYERTASK PlayerTask
|
||||
-- @param #boolean Silent If true, make no "has new task" announcement
|
||||
-- @param #boolen TaskFilter If true, apply the white/black-list task filters here, also
|
||||
-- @return #PLAYERTASKCONTROLLER self
|
||||
-- @usage
|
||||
-- Example to create a PLAYERTASK of type CTLD and give Players 10 minutes to complete:
|
||||
@ -2672,9 +2771,17 @@ end
|
||||
-- )
|
||||
--
|
||||
-- taskmanager:AddPlayerTaskToQueue(PlayerTask)
|
||||
function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent)
|
||||
function PLAYERTASKCONTROLLER:AddPlayerTaskToQueue(PlayerTask,Silent,TaskFilter)
|
||||
self:T(self.lid.."AddPlayerTaskToQueue")
|
||||
if PlayerTask and PlayerTask.ClassName and PlayerTask.ClassName == "PLAYERTASK" then
|
||||
if TaskFilter then
|
||||
if self.UseWhiteList and (not self:_CheckTaskTypeAllowed(PlayerTask.Type)) then
|
||||
return self
|
||||
end
|
||||
if self.UseBlackList and self:_CheckTaskTypeDisallowed(PlayerTask.Type) then
|
||||
return self
|
||||
end
|
||||
end
|
||||
PlayerTask:_SetController(self)
|
||||
PlayerTask:SetCoalition(self.Coalition)
|
||||
self.TaskQueue:Push(PlayerTask)
|
||||
@ -3120,25 +3227,26 @@ end
|
||||
-- @param Core.Menu#MENU_BASE topmenu
|
||||
-- @param #table tasktypes
|
||||
-- @param #table taskpertype
|
||||
-- @param #string newtag
|
||||
-- @return #table taskinfomenu
|
||||
function PLAYERTASKCONTROLLER:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype)
|
||||
function PLAYERTASKCONTROLLER:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype,newtag)
|
||||
self:T(self.lid.."_BuildTaskInfoMenu")
|
||||
local taskinfomenu = nil
|
||||
if self.taskinfomenu then
|
||||
local menutaskinfo = self.gettext:GetEntry("MENUTASKINFO",self.locale)
|
||||
local taskinfomenu = MENU_GROUP_DELAYED:New(group,menutaskinfo,topmenu)
|
||||
local taskinfomenu = MENU_GROUP_DELAYED:New(group,menutaskinfo,topmenu):SetTag(newtag)
|
||||
local ittypes = {}
|
||||
local itaskmenu = {}
|
||||
local tnow = timer.getTime()
|
||||
|
||||
for _tasktype,_data in pairs(tasktypes) do
|
||||
ittypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,taskinfomenu)
|
||||
ittypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,taskinfomenu):SetTag(newtag)
|
||||
local tasks = taskpertype[_tasktype] or {}
|
||||
local n = 0
|
||||
for _,_task in pairs(tasks) do
|
||||
_task = _task -- Ops.PlayerTask#PLAYERTASK
|
||||
local pilotcount = _task:CountClients()
|
||||
local newtext = "]"
|
||||
local tnow = timer.getTime()
|
||||
-- marker for new tasks
|
||||
if tnow - _task.timestamp < 60 then
|
||||
newtext = "*]"
|
||||
@ -3151,7 +3259,14 @@ function PLAYERTASKCONTROLLER:_BuildTaskInfoMenu(group,client,playername,topmenu
|
||||
text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext)
|
||||
end
|
||||
end
|
||||
local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ittypes[_tasktype],self._ActiveTaskInfo,self,group,client,_task)
|
||||
if self.UseTypeNames then
|
||||
if _task.TypeName then
|
||||
--local name = self.gettext:GetEntry(_task.TypeName,self.locale)
|
||||
text = string.format("%s (%03d) [%d%s",_task.TypeName,_task.PlayerTaskNr,pilotcount,newtext)
|
||||
--self:T(self.lid.."Menu text = "..text)
|
||||
end
|
||||
end
|
||||
local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ittypes[_tasktype],self._ActiveTaskInfo,self,group,client,_task):SetTag(newtag)
|
||||
--taskentry:SetTag(playername)
|
||||
itaskmenu[#itaskmenu+1] = taskentry
|
||||
-- keep max items limit
|
||||
@ -3186,11 +3301,16 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
end
|
||||
|
||||
for _,_client in pairs(clients) do
|
||||
if _client then
|
||||
if _client and _client:IsAlive() then
|
||||
local client = _client -- Wrapper.Client#CLIENT
|
||||
local group = client:GetGroup()
|
||||
local unknown = self.gettext:GetEntry("UNKNOWN",self.locale)
|
||||
local playername = client:GetPlayerName() or unknown
|
||||
|
||||
local oldtag = self.PlayerMenuTag[playername]
|
||||
local newtag = playername..timer.getAbsTime()
|
||||
self.PlayerMenuTag[playername] = newtag
|
||||
|
||||
if group and client then
|
||||
---
|
||||
-- TOPMENU
|
||||
@ -3202,6 +3322,8 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
|
||||
if self:_CheckPlayerHasTask(playername) and not fromsuccess then playerhastask = true end
|
||||
local topmenu = nil
|
||||
--local oldmenu = nil
|
||||
local rebuilddone = false
|
||||
|
||||
self:T("Playerhastask = "..tostring(playerhastask).." Enforced = "..tostring(enforced).." Join or Abort = "..tostring(joinorabort))
|
||||
|
||||
@ -3215,16 +3337,20 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
-- 2)+3) Join or abort?
|
||||
if joinorabort then
|
||||
self.PlayerMenu[playername]:RemoveSubMenus()
|
||||
self.PlayerMenu[playername]:SetTag(timer.getAbsTime())
|
||||
self.PlayerMenu[playername]:SetTag(newtag)
|
||||
topmenu = self.PlayerMenu[playername]
|
||||
elseif (not playerhastask) or enforced then
|
||||
-- 4) last build > 30 secs?
|
||||
local T0 = timer.getAbsTime()
|
||||
local TDiff = T0-self.PlayerMenu[playername].MenuTag
|
||||
local TDiff = T0-self.PlayerMenu[playername].PTTimeStamp
|
||||
self:T("TDiff = "..string.format("%.2d",TDiff))
|
||||
if TDiff >= self.holdmenutime then
|
||||
self.PlayerMenu[playername]:RemoveSubMenus()
|
||||
self.PlayerMenu[playername]:SetTag(timer.getAbsTime())
|
||||
--self.PlayerMenu[playername]:RemoveSubMenus()
|
||||
--oldmenu = self.PlayerMenu[playername]
|
||||
--self.PlayerMenu[playername] = nil
|
||||
self.PlayerMenu[playername] = MENU_GROUP_DELAYED:New(group,menuname,self.MenuParent)
|
||||
self.PlayerMenu[playername]:SetTag(newtag)
|
||||
self.PlayerMenu[playername].PTTimeStamp = timer.getAbsTime()
|
||||
timedbuild = true
|
||||
end
|
||||
topmenu = self.PlayerMenu[playername]
|
||||
@ -3233,14 +3359,17 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
-- 1) new player#
|
||||
topmenu = MENU_GROUP_DELAYED:New(group,menuname,self.MenuParent)
|
||||
self.PlayerMenu[playername] = topmenu
|
||||
self.PlayerMenu[playername]:SetTag(timer.getAbsTime())
|
||||
self.PlayerMenu[playername]:SetTag(newtag)
|
||||
self.PlayerMenu[playername].PTTimeStamp = timer.getAbsTime()
|
||||
enforced = true
|
||||
end
|
||||
|
||||
---
|
||||
-- ACTIVE TASK MENU
|
||||
---
|
||||
if playerhastask and enforced then
|
||||
--self:T("Building Active Task Menus for "..playername)
|
||||
self:T("Building Active Task Menus for "..playername)
|
||||
rebuilddone = true
|
||||
local menuactive = self.gettext:GetEntry("MENUACTIVE",self.locale)
|
||||
local menuinfo = self.gettext:GetEntry("MENUINFO",self.locale)
|
||||
local menumark = self.gettext:GetEntry("MENUMARK",self.locale)
|
||||
@ -3248,44 +3377,45 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
local menuflare = self.gettext:GetEntry("MENUFLARE",self.locale)
|
||||
local menuabort = self.gettext:GetEntry("MENUABORT",self.locale)
|
||||
|
||||
local active = MENU_GROUP_DELAYED:New(group,menuactive,topmenu)
|
||||
local info = MENU_GROUP_COMMAND_DELAYED:New(group,menuinfo,active,self._ActiveTaskInfo,self,group,client)
|
||||
local mark = MENU_GROUP_COMMAND_DELAYED:New(group,menumark,active,self._MarkTask,self,group,client)
|
||||
local active = MENU_GROUP_DELAYED:New(group,menuactive,topmenu):SetTag(newtag)
|
||||
local info = MENU_GROUP_COMMAND_DELAYED:New(group,menuinfo,active,self._ActiveTaskInfo,self,group,client):SetTag(newtag)
|
||||
local mark = MENU_GROUP_COMMAND_DELAYED:New(group,menumark,active,self._MarkTask,self,group,client):SetTag(newtag)
|
||||
if self.Type ~= PLAYERTASKCONTROLLER.Type.A2A then
|
||||
if self.noflaresmokemenu ~= true then
|
||||
-- no smoking/flaring here if A2A or designer has set noflaresmokemenu to true
|
||||
local smoke = MENU_GROUP_COMMAND_DELAYED:New(group,menusmoke,active,self._SmokeTask,self,group,client)
|
||||
local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client)
|
||||
local smoke = MENU_GROUP_COMMAND_DELAYED:New(group,menusmoke,active,self._SmokeTask,self,group,client):SetTag(newtag)
|
||||
local flare = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._FlareTask,self,group,client):SetTag(newtag)
|
||||
local IsNight = client:GetCoordinate():IsNight()
|
||||
if IsNight then
|
||||
local light = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._IlluminateTask,self,group,client)
|
||||
local light = MENU_GROUP_COMMAND_DELAYED:New(group,menuflare,active,self._IlluminateTask,self,group,client):SetTag(newtag)
|
||||
end
|
||||
end
|
||||
end
|
||||
local abort = MENU_GROUP_COMMAND_DELAYED:New(group,menuabort,active,self._AbortTask,self,group,client)
|
||||
local abort = MENU_GROUP_COMMAND_DELAYED:New(group,menuabort,active,self._AbortTask,self,group,client):SetTag(newtag)
|
||||
if self.activehasinfomenu and self.taskinfomenu then
|
||||
--self:T("Building Active-Info Menus for "..playername)
|
||||
self:T("Building Active-Info Menus for "..playername)
|
||||
local tasktypes = self:_GetAvailableTaskTypes()
|
||||
local taskpertype = self:_GetTasksPerType()
|
||||
if self.PlayerInfoMenu[playername] then
|
||||
self.PlayerInfoMenu[playername]:RemoveSubMenus()
|
||||
self.PlayerInfoMenu[playername]:RemoveSubMenus(nil,oldtag)
|
||||
end
|
||||
self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype)
|
||||
self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype,newtag)
|
||||
end
|
||||
elseif (self.TaskQueue:Count() > 0 and enforced) or (not playerhastask and (timedbuild or joinorabort)) then
|
||||
--self:T("Building Join Menus for "..playername)
|
||||
self:T("Building Join Menus for "..playername)
|
||||
rebuilddone = true
|
||||
---
|
||||
-- JOIN TASK MENU
|
||||
---
|
||||
local tasktypes = self:_GetAvailableTaskTypes()
|
||||
local taskpertype = self:_GetTasksPerType()
|
||||
local menujoin = self.gettext:GetEntry("MENUJOIN",self.locale)
|
||||
local joinmenu = MENU_GROUP_DELAYED:New(group,menujoin,topmenu)
|
||||
local joinmenu = MENU_GROUP_DELAYED:New(group,menujoin,topmenu):SetTag(newtag)
|
||||
|
||||
local ttypes = {}
|
||||
local taskmenu = {}
|
||||
for _tasktype,_data in pairs(tasktypes) do
|
||||
ttypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,joinmenu)
|
||||
ttypes[_tasktype] = MENU_GROUP_DELAYED:New(group,_tasktype,joinmenu):SetTag(newtag)
|
||||
local tasks = taskpertype[_tasktype] or {}
|
||||
local n = 0
|
||||
for _,_task in pairs(tasks) do
|
||||
@ -3305,7 +3435,7 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
text = string.format("%s (%03d) [%d%s",name,_task.PlayerTaskNr,pilotcount,newtext)
|
||||
end
|
||||
end
|
||||
local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ttypes[_tasktype],self._JoinTask,self,group,client,_task)
|
||||
local taskentry = MENU_GROUP_COMMAND_DELAYED:New(group,text,ttypes[_tasktype],self._JoinTask,self,group,client,_task):SetTag(newtag)
|
||||
--taskentry:SetTag(playername)
|
||||
taskmenu[#taskmenu+1] = taskentry
|
||||
n = n + 1
|
||||
@ -3315,25 +3445,30 @@ function PLAYERTASKCONTROLLER:_BuildMenus(Client,enforced,fromsuccess)
|
||||
end
|
||||
end
|
||||
if self.taskinfomenu then
|
||||
--self:T("Building Join-Info Menus for "..playername)
|
||||
self:T("Building Join-Info Menus for "..playername)
|
||||
if self.PlayerInfoMenu[playername] then
|
||||
self.PlayerInfoMenu[playername]:RemoveSubMenus()
|
||||
self.PlayerInfoMenu[playername]:RemoveSubMenus(nil,oldtag)
|
||||
end
|
||||
self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype)
|
||||
self.PlayerInfoMenu[playername] = self:_BuildTaskInfoMenu(group,client,playername,topmenu,tasktypes,taskpertype,newtag)
|
||||
end
|
||||
elseif self.TaskQueue:Count() == 0 then
|
||||
-- no tasks (yet)
|
||||
end
|
||||
if self.AllowFlash then
|
||||
local flashtext = self.gettext:GetEntry("FLASHMENU",self.locale)
|
||||
local flashmenu = MENU_GROUP_COMMAND_DELAYED:New(group,flashtext,topmenu,self._SwitchFlashing,self,group,client):SetTag(newtag)
|
||||
end
|
||||
if self.TaskQueue:Count() == 0 then
|
||||
self:T("No open tasks info")
|
||||
local menunotasks = self.gettext:GetEntry("MENUNOTASKS",self.locale)
|
||||
local joinmenu = MENU_GROUP_DELAYED:New(group,menunotasks,topmenu)
|
||||
local joinmenu = MENU_GROUP_DELAYED:New(group,menunotasks,self.PlayerMenu[playername]):SetTag(newtag)
|
||||
rebuilddone = true
|
||||
end
|
||||
---
|
||||
-- REFRESH MENU
|
||||
---
|
||||
if self.AllowFlash then
|
||||
local flashtext = self.gettext:GetEntry("FLASHMENU",self.locale)
|
||||
local flashmenu = MENU_GROUP_COMMAND_DELAYED:New(group,flashtext,self.PlayerMenu[playername],self._SwitchFlashing,self,group,client)
|
||||
if rebuilddone then
|
||||
self.PlayerMenu[playername]:RemoveSubMenus(nil,oldtag)
|
||||
self.PlayerMenu[playername]:Refresh()
|
||||
end
|
||||
self.PlayerMenu[playername]:Set()
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -3652,7 +3787,7 @@ function PLAYERTASKCONTROLLER:onafterStatus(From, Event, To)
|
||||
self:_BuildMenus(nil,enforcedmenu)
|
||||
|
||||
if self.verbose then
|
||||
local text = string.format("New Targets: %02d | Active Tasks: %02d | Active Players: %02d | Assigned Tasks: %02d",targetcount,taskcount,playercount,assignedtasks)
|
||||
local text = string.format("%s | New Targets: %02d | Active Tasks: %02d | Active Players: %02d | Assigned Tasks: %02d",self.MenuName, targetcount,taskcount,playercount,assignedtasks)
|
||||
self:I(text)
|
||||
end
|
||||
|
||||
|
||||
@ -625,7 +625,7 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
-- Log output verbose=1.
|
||||
if self.verbose>=1 then
|
||||
local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.N0, self:GetLife(), self:GetLife0(), self:GetDamage())
|
||||
if self:CountTargets() == 0 then
|
||||
if self:CountTargets() == 0 or self:GetDamage() >= 100 then
|
||||
text=text.." Dead!"
|
||||
elseif damaged then
|
||||
text=text.." Damaged!"
|
||||
@ -644,7 +644,7 @@ function TARGET:onafterStatus(From, Event, To)
|
||||
self:I(self.lid..text)
|
||||
end
|
||||
|
||||
if self:CountTargets() == 0 then
|
||||
if self:CountTargets() == 0 or self:GetDamage() >= 100 then
|
||||
self:Dead()
|
||||
end
|
||||
|
||||
@ -943,6 +943,9 @@ function TARGET:_AddObject(Object)
|
||||
target.Coordinate=scenery:GetCoordinate()
|
||||
|
||||
target.Life0=scenery:GetLife0()
|
||||
|
||||
if target.Life0==0 then target.Life0 = 1 end
|
||||
|
||||
target.Life=scenery:GetLife()
|
||||
|
||||
target.N0=target.N0+1
|
||||
@ -1095,7 +1098,9 @@ function TARGET:GetTargetLife(Target)
|
||||
elseif Target.Type==TARGET.ObjectType.STATIC then
|
||||
|
||||
if Target.Object and Target.Object:IsAlive() then
|
||||
return 1
|
||||
local life=Target.Object:GetLife()
|
||||
return life
|
||||
--return 1
|
||||
else
|
||||
return 0
|
||||
end
|
||||
|
||||
@ -2201,10 +2201,29 @@ function UTILS.CheckFileExists(Path,Filename)
|
||||
end
|
||||
end
|
||||
|
||||
--- Function to obtain a table of typenames from the group given with the number of units of the same type in the group.
|
||||
-- @param Wrapper.Group#GROUP Group The group to list
|
||||
-- @return #table Table of typnames and typename counts, e.g. `{["KAMAZ Truck"]=3,["ATZ-5"]=1}`
|
||||
function UTILS.GetCountPerTypeName(Group)
|
||||
local units = Group:GetUnits()
|
||||
local TypeNameTable = {}
|
||||
for _,_unt in pairs (units) do
|
||||
local unit = _unt -- Wrapper.Unit#UNIT
|
||||
local typen = unit:GetTypeName()
|
||||
if not TypeNameTable[typen] then
|
||||
TypeNameTable[typen] = 1
|
||||
else
|
||||
TypeNameTable[typen] = TypeNameTable[typen] + 1
|
||||
end
|
||||
end
|
||||
return TypeNameTable
|
||||
end
|
||||
|
||||
--- Function to save the state of a list of groups found by name
|
||||
-- @param #table List Table of strings with groupnames
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
-- @usage
|
||||
-- We will go through the list and find the corresponding group and save the current group size (0 when dead).
|
||||
@ -2212,7 +2231,7 @@ end
|
||||
-- Position is still saved for your usage.
|
||||
-- The idea is to reduce the number of units when reloading the data again to restart the saved mission.
|
||||
-- The data will be a simple comma separated list of groupname and size, with one header line.
|
||||
function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
|
||||
function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured)
|
||||
local filename = Filename or "StateListofGroups"
|
||||
local data = "--Save Stationary List of Groups: "..Filename .."\n"
|
||||
for _,_group in pairs (List) do
|
||||
@ -2220,7 +2239,16 @@ function UTILS.SaveStationaryListOfGroups(List,Path,Filename)
|
||||
if group and group:IsAlive() then
|
||||
local units = group:CountAliveUnits()
|
||||
local position = group:GetVec3()
|
||||
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
|
||||
if Structured then
|
||||
local structure = UTILS.GetCountPerTypeName(group)
|
||||
local strucdata = ""
|
||||
for typen,anzahl in pairs (structure) do
|
||||
strucdata = strucdata .. typen .. "=="..anzahl..";"
|
||||
end
|
||||
data = string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata)
|
||||
else
|
||||
data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z)
|
||||
end
|
||||
else
|
||||
data = string.format("%s%s,0,0,0,0\n",data,_group)
|
||||
end
|
||||
@ -2234,6 +2262,7 @@ end
|
||||
-- @param Core.Set#SET_BASE Set of objects to save
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Structured Append the data with a list of typenames in the group plus their count.
|
||||
-- @return #boolean outcome True if saving is successful, else false.
|
||||
-- @usage
|
||||
-- We will go through the set and find the corresponding group and save the current group size and current position.
|
||||
@ -2243,7 +2272,7 @@ end
|
||||
-- **Note** Do NOT use dashes or hashes in group template names (-,#)!
|
||||
-- The data will be a simple comma separated list of groupname and size, with one header line.
|
||||
-- The current task/waypoint/etc cannot be restored.
|
||||
function UTILS.SaveSetOfGroups(Set,Path,Filename)
|
||||
function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured)
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local data = "--Save SET of groups: "..Filename .."\n"
|
||||
local List = Set:GetSetObjects()
|
||||
@ -2257,7 +2286,16 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename)
|
||||
end
|
||||
local units = group:CountAliveUnits()
|
||||
local position = group:GetVec3()
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
|
||||
if Structured then
|
||||
local structure = UTILS.GetCountPerTypeName(group)
|
||||
local strucdata = ""
|
||||
for typen,anzahl in pairs (structure) do
|
||||
strucdata = strucdata .. typen .. "=="..anzahl..";"
|
||||
end
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata)
|
||||
else
|
||||
data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z)
|
||||
end
|
||||
end
|
||||
end
|
||||
-- save the data
|
||||
@ -2321,8 +2359,41 @@ end
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number.
|
||||
-- @param #boolean Structured (Optional, needs Reduce = true) If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
|
||||
-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read.
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density)
|
||||
|
||||
local fires = {}
|
||||
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
local function Cruncher(group,typename,anzahl)
|
||||
local units = group:GetUnits()
|
||||
local reduced = 0
|
||||
for _,_unit in pairs (units) do
|
||||
local typo = _unit:GetTypeName()
|
||||
if typename == typo then
|
||||
if Cinematic then
|
||||
local coordinate = _unit:GetCoordinate()
|
||||
local name = _unit:GetName()
|
||||
Smokers(name,coordinate,Effect,Density)
|
||||
end
|
||||
_unit:Destroy(false)
|
||||
reduced = reduced + 1
|
||||
if reduced == anzahl then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local reduce = true
|
||||
if Reduce == false then reduce = false end
|
||||
local filename = Filename or "StateListofGroups"
|
||||
@ -2339,18 +2410,48 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
|
||||
local posx = tonumber(dataset[3])
|
||||
local posy = tonumber(dataset[4])
|
||||
local posz = tonumber(dataset[5])
|
||||
local structure = dataset[6]
|
||||
--BASE:I({structure})
|
||||
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) }
|
||||
if reduce then
|
||||
local actualgroup = GROUP:FindByName(groupname)
|
||||
if actualgroup and actualgroup:IsAlive() and actualgroup:CountAliveUnits() > size then
|
||||
local reduction = actualgroup:CountAliveUnits() - size
|
||||
BASE:I("Reducing groupsize by ".. reduction .. " units!")
|
||||
-- reduce existing group
|
||||
local units = actualgroup:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
if Structured and structure then
|
||||
--BASE:I("Reducing group structure!")
|
||||
local loadedstructure = {}
|
||||
local strcset = UTILS.Split(structure,";")
|
||||
for _,_data in pairs(strcset) do
|
||||
local datasplit = UTILS.Split(_data,"==")
|
||||
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
|
||||
end
|
||||
--BASE:I({loadedstructure})
|
||||
local originalstructure = UTILS.GetCountPerTypeName(actualgroup)
|
||||
--BASE:I({originalstructure})
|
||||
for _name,_number in pairs(originalstructure) do
|
||||
local loadednumber = 0
|
||||
if loadedstructure[_name] then
|
||||
loadednumber = loadedstructure[_name]
|
||||
end
|
||||
local reduce = false
|
||||
if loadednumber < _number then reduce = true end
|
||||
|
||||
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
|
||||
|
||||
if reduce then
|
||||
Cruncher(actualgroup,_name,_number-loadednumber)
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
local reduction = actualgroup:CountAliveUnits() - size
|
||||
--BASE:I("Reducing groupsize by ".. reduction .. " units!")
|
||||
-- reduce existing group
|
||||
local units = actualgroup:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -2359,19 +2460,52 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce)
|
||||
else
|
||||
return nil
|
||||
end
|
||||
return datatable
|
||||
return datatable,fires
|
||||
end
|
||||
|
||||
--- Load back a SET of groups from file.
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size.
|
||||
-- @param #boolean Structured (Optional, needs Spawn=true)If true, and the data has been saved as structure before, remove the correct unit types as per the saved list.
|
||||
-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return Core.Set#SET_GROUP Set of GROUP objects.
|
||||
-- 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 }`
|
||||
function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
|
||||
-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )`
|
||||
function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density)
|
||||
|
||||
local fires = {}
|
||||
|
||||
local function Smokers(name,coord,effect,density)
|
||||
local eff = math.random(8)
|
||||
if type(effect) == "number" then eff = effect end
|
||||
coord:BigSmokeAndFire(eff,density,name)
|
||||
table.insert(fires,name)
|
||||
end
|
||||
|
||||
local function Cruncher(group,typename,anzahl)
|
||||
local units = group:GetUnits()
|
||||
local reduced = 0
|
||||
for _,_unit in pairs (units) do
|
||||
local typo = _unit:GetTypeName()
|
||||
if typename == typo then
|
||||
if Cinematic then
|
||||
local coordinate = _unit:GetCoordinate()
|
||||
local name = _unit:GetName()
|
||||
Smokers(name,coordinate,Effect,Density)
|
||||
end
|
||||
_unit:Destroy(false)
|
||||
reduced = reduced + 1
|
||||
if reduced == anzahl then break end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local spawn = true
|
||||
if Spawn == false then spawn = false end
|
||||
BASE:I("Spawn = "..tostring(spawn))
|
||||
--BASE:I("Spawn = "..tostring(spawn))
|
||||
local filename = Filename or "SetOfGroups"
|
||||
local setdata = SET_GROUP:New()
|
||||
local datatable = {}
|
||||
@ -2388,6 +2522,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
|
||||
local posx = tonumber(dataset[4])
|
||||
local posy = tonumber(dataset[5])
|
||||
local posz = tonumber(dataset[6])
|
||||
local structure = dataset[7]
|
||||
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
local group=nil
|
||||
local data = { groupname=groupname, size=size, coordinate=coordinate, template=template }
|
||||
@ -2400,12 +2535,40 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
|
||||
setdata:AddObject(spwndgrp)
|
||||
local actualsize = spwndgrp:CountAliveUnits()
|
||||
if actualsize > size then
|
||||
local reduction = actualsize-size
|
||||
-- reduce existing group
|
||||
local units = spwndgrp:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
if Structured and structure then
|
||||
--BASE:I("Reducing group structure!")
|
||||
local loadedstructure = {}
|
||||
local strcset = UTILS.Split(structure,";")
|
||||
for _,_data in pairs(strcset) do
|
||||
local datasplit = UTILS.Split(_data,"==")
|
||||
loadedstructure[datasplit[1]] = tonumber(datasplit[2])
|
||||
end
|
||||
--BASE:I({loadedstructure})
|
||||
local originalstructure = UTILS.GetCountPerTypeName(spwndgrp)
|
||||
--BASE:I({originalstructure})
|
||||
for _name,_number in pairs(originalstructure) do
|
||||
local loadednumber = 0
|
||||
if loadedstructure[_name] then
|
||||
loadednumber = loadedstructure[_name]
|
||||
end
|
||||
local reduce = false
|
||||
if loadednumber < _number then reduce = true end
|
||||
|
||||
--BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce)))
|
||||
|
||||
if reduce then
|
||||
Cruncher(spwndgrp,_name,_number-loadednumber)
|
||||
end
|
||||
|
||||
end
|
||||
else
|
||||
local reduction = actualsize-size
|
||||
-- reduce existing group
|
||||
local units = spwndgrp:GetUnits()
|
||||
local units2 = UTILS.ShuffleTable(units) -- randomize table
|
||||
for i=1,reduction do
|
||||
units2[i]:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@ -2417,7 +2580,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn)
|
||||
return nil
|
||||
end
|
||||
if spawn then
|
||||
return setdata
|
||||
return setdata,fires
|
||||
else
|
||||
return datatable
|
||||
end
|
||||
@ -2436,13 +2599,11 @@ function UTILS.LoadSetOfStatics(Path,Filename)
|
||||
table.remove(loadeddata, 1)
|
||||
for _id,_entry in pairs (loadeddata) do
|
||||
local dataset = UTILS.Split(_entry,",")
|
||||
-- staticname,position.x,position.y,position.z
|
||||
local staticname = dataset[1]
|
||||
local posx = tonumber(dataset[2])
|
||||
local posy = tonumber(dataset[3])
|
||||
local posz = tonumber(dataset[4])
|
||||
local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz})
|
||||
datatable:AddObject(STATIC:FindByName(staticname,false))
|
||||
local StaticObject = STATIC:FindByName(staticname,false)
|
||||
if StaticObject then
|
||||
datatable:AddObject(StaticObject)
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
@ -2454,9 +2615,15 @@ end
|
||||
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
|
||||
-- @param #string Filename The name of the file.
|
||||
-- @param #boolean Reduce If false, do not destroy the units with size=0.
|
||||
-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object.
|
||||
-- @param #boolean Dead (Optional, needs Reduce = true) If Dead is true, re-spawn the dead object as dead and do not just delete it.
|
||||
-- @param #boolean Cinematic (Optional, needs Dead = true) If true, place a fire/smoke effect on the dead static position.
|
||||
-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke.
|
||||
-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5.
|
||||
-- @return #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 )`
|
||||
-- Returns nil when file cannot be read.
|
||||
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
|
||||
function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density)
|
||||
local fires = {}
|
||||
local reduce = true
|
||||
if Reduce == false then reduce = false end
|
||||
local filename = Filename or "StateListofStatics"
|
||||
@ -2479,14 +2646,31 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce)
|
||||
if size==0 and reduce then
|
||||
local static = STATIC:FindByName(staticname,false)
|
||||
if static then
|
||||
static:Destroy(false)
|
||||
if Dead then
|
||||
local deadobject = SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry())
|
||||
deadobject:InitDead(true)
|
||||
local heading = static:GetHeading()
|
||||
local coord = static:GetCoordinate()
|
||||
static:Destroy(false)
|
||||
deadobject:SpawnFromCoordinate(coord,heading,staticname)
|
||||
if Cinematic then
|
||||
local effect = math.random(8)
|
||||
if type(Effect) == "number" then
|
||||
effect = Effect
|
||||
end
|
||||
coord:BigSmokeAndFire(effect,Density,staticname)
|
||||
table.insert(fires,staticname)
|
||||
end
|
||||
else
|
||||
static:Destroy(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
else
|
||||
return nil
|
||||
end
|
||||
return datatable
|
||||
return datatable,fires
|
||||
end
|
||||
|
||||
--- Heading Degrees (0-360) to Cardinal
|
||||
|
||||
@ -510,6 +510,7 @@ AIRBASE.MarianaIslands = {
|
||||
-- * AIRBASE.SouthAtlantic.Porvenir_Airfield
|
||||
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Turbio
|
||||
-- * AIRBASE.SouthAtlantic.Rio_Chico_Airfield
|
||||
--
|
||||
--@field MarianaIslands
|
||||
AIRBASE.SouthAtlantic={
|
||||
@ -532,6 +533,7 @@ AIRBASE.SouthAtlantic={
|
||||
["Porvenir_Airfield"]="Porvenir Airfield",
|
||||
["Almirante_Schroeders"]="Almirante Schroeders",
|
||||
["Rio_Turbio"]="Rio Turbio",
|
||||
["Rio_Chico"] = "Rio Chico",
|
||||
}
|
||||
|
||||
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
|
||||
@ -839,7 +841,7 @@ end
|
||||
-- Black listed spots overrule white listed spots.
|
||||
-- **NOTE** that terminal IDs are not necessarily the same as those displayed in the mission editor!
|
||||
-- @param #AIRBASE self
|
||||
-- @param #table TerminalIdBlacklist Table of white listed terminal IDs.
|
||||
-- @param #table TerminalIdWhitelist Table of white listed terminal IDs.
|
||||
-- @return #AIRBASE self
|
||||
-- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotWhitelist({2, 3, 4}) --Only allow terminal IDs 2, 3, 4
|
||||
function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist)
|
||||
|
||||
@ -630,21 +630,23 @@ end
|
||||
-- @param Wrapper.Unit#UNIT self
|
||||
-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
|
||||
function UNIT:GetGroup()
|
||||
self:F2( self.UnitName )
|
||||
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
|
||||
if DCSUnit then
|
||||
local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() )
|
||||
self:F2( self.UnitName )
|
||||
local UnitGroup = GROUP:FindByName(self.GroupName)
|
||||
if UnitGroup then
|
||||
return UnitGroup
|
||||
else
|
||||
local DCSUnit = self:GetDCSObject()
|
||||
if DCSUnit then
|
||||
local grp = DCSUnit:getGroup()
|
||||
if grp then
|
||||
local UnitGroup = GROUP:FindByName( grp:getName() )
|
||||
return UnitGroup
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
-- Need to add here functions to check if radar is on and which object etc.
|
||||
|
||||
--- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign.
|
||||
-- DCS Units spawned with the @{Core.Spawn#SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name.
|
||||
-- The spawn sequence number and unit number are contained within the name after the '#' sign.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user