Merge branch 'develop' into FF/Ops

This commit is contained in:
Frank 2022-12-30 22:08:41 +01:00
commit 3834991e6a
14 changed files with 848 additions and 264 deletions

View File

@ -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)
--
-- ===
--

View File

@ -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 )

View File

@ -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.

View File

@ -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, ";", " . " )

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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.