Merge pull request #1747 from FlightControl-Master/FF/Ops

OPS
This commit is contained in:
Frank 2022-07-20 22:58:57 +02:00 committed by GitHub
commit 420526df9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 9291 additions and 725 deletions

View File

@ -0,0 +1,295 @@
--- **Core** - Define any or all conditions to be evaluated.
--
-- **Main Features:**
--
-- * Add arbitrary numbers of conditon functions
-- * Evaluate *any* or *all* conditions
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Operation).
--
-- ===
--
-- ### Author: **funkyfranky**
--
-- ===
-- @module Core.Condition
-- @image Core_Conditon.png
--- CONDITON class.
-- @type CONDITION
-- @field #string ClassName Name of the class.
-- @field #string lid Class id string for output to DCS log file.
-- @field #boolean isAny General functions are evaluated as any condition.
-- @field #boolean negateResult Negeate result of evaluation.
-- @field #table functionsGen General condition functions.
-- @field #table functionsAny Any condition functions.
-- @field #table functionsAll All condition functions.
--
-- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* - William Shakespeare
--
-- ===
--
-- # The CONDITION Concept
--
--
--
-- @field #CONDITION
CONDITION = {
ClassName = "CONDITION",
lid = nil,
functionsGen = {},
functionsAny = {},
functionsAll = {},
}
--- Condition function.
-- @type CONDITION.Function
-- @field #function func Callback function to check for a condition. Should return a `#boolean`.
-- @field #table arg (Optional) Arguments passed to the condition callback function if any.
--- CONDITION class version.
-- @field #string version
CONDITION.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Make FSM.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new CONDITION object.
-- @param #CONDITION self
-- @param #string Name (Optional) Name used in the logs.
-- @return #CONDITION self
function CONDITION:New(Name)
-- Inherit BASE.
local self=BASE:Inherit(self, BASE:New()) --#CONDITION
self.name=Name or "Condition X"
self.lid=string.format("%s | ", self.name)
return self
end
--- Set that general condition functions return `true` if `any` function returns `true`. Default is that *all* functions must return `true`.
-- @param #CONDITION self
-- @param #boolean Any If `true`, *any* condition can be true. Else *all* conditions must result `true`.
-- @return #CONDITION self
function CONDITION:SetAny(Any)
self.isAny=Any
return self
end
--- Negate result.
-- @param #CONDITION self
-- @param #boolean Negate If `true`, result is negated else not.
-- @return #CONDITION self
function CONDITION:SetNegateResult(Negate)
self.negateResult=Negate
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
--
-- @usage
-- local function isAequalB(a, b)
-- return a==b
-- end
--
-- myCondition:AddFunction(isAequalB, a, b)
--
-- @return #CONDITION self
function CONDITION:AddFunction(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsGen, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAny(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAny, condition)
return self
end
--- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`).
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION self
function CONDITION:AddFunctionAll(Function, ...)
-- Condition function.
local condition=self:_CreateCondition(Function, ...)
-- Add to table.
table.insert(self.functionsAll, condition)
return self
end
--- Evaluate conditon functions.
-- @param #CONDITION self
-- @param #boolean AnyTrue If `true`, evaluation return `true` if *any* condition function returns `true`. By default, *all* condition functions must return true.
-- @return #boolean Result of condition functions.
function CONDITION:Evaluate(AnyTrue)
-- Check if at least one function was given.
if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then
if self.negateResult then
return true
else
return false
end
end
-- Any condition for gen.
local evalAny=self.isAny
if AnyTrue~=nil then
evalAny=AnyTrue
end
local isGen=nil
if evalAny then
isGen=self:_EvalConditionsAny(self.functionsGen)
else
isGen=self:_EvalConditionsAll(self.functionsGen)
end
-- Is any?
local isAny=self:_EvalConditionsAny(self.functionsAny)
-- Is all?
local isAll=self:_EvalConditionsAll(self.functionsAll)
-- Result.
local result=isGen and isAny and isAll
-- Negate result.
if self.negateResult then
result=not result
end
-- Debug message.
self:T(self.lid..string.format("Evaluate: isGen=%s, isAny=%s, isAll=%s (negate=%s) ==> result=%s", tostring(isGen), tostring(isAny), tostring(isAll), tostring(self.negateResult), tostring(result)))
return result
end
--- Check if all given condition are true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, all conditions were true (or functions was empty/nil). Returns false if at least one condition returned false.
function CONDITION:_EvalConditionsAll(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any false will return false.
if not istrue then
return false
end
end
-- All conditions were true.
return true
end
--- Check if any of the given conditions is true.
-- @param #CONDITION self
-- @param #table functions Functions to evaluate.
-- @return #boolean If true, at least one condition is true (or functions was emtpy/nil).
function CONDITION:_EvalConditionsAny(functions)
-- At least one condition?
local gotone=false
-- Any stop condition must be true.
for _,_condition in pairs(functions or {}) do
local condition=_condition --#CONDITION.Function
-- At least one condition was defined.
gotone=true
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any true will return true.
if istrue then
return true
end
end
-- No condition was true.
if gotone then
return false
else
-- No functions passed.
return true
end
end
--- Create conditon fucntion object.
-- @param #CONDITION self
-- @param #function Function The function to call.
-- @param ... (Optional) Parameters passed to the function (if any).
-- @return #CONDITION.Function Condition function.
function CONDITION:_CreateCondition(Function, ...)
local condition={} --#CONDITION.Function
condition.func=Function
condition.arg={}
if arg then
condition.arg=arg
end
return condition
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -1027,7 +1027,7 @@ function DATABASE:_RegisterAirbases()
local airbaseUID=airbase:GetID(true)
-- Debug output.
local text=string.format("Register %s: %s (ID=%d UID=%d), parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseID, airbaseUID, airbase.NparkingTotal)
local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[airbase.category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal)
for _,terminalType in pairs(AIRBASE.TerminalType) do
if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then
text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType])
@ -1533,6 +1533,33 @@ function DATABASE:FindOpsGroup(groupname)
return self.FLIGHTGROUPS[groupname]
end
--- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base for a given unit.
-- @param #DATABASE self
-- @param #string unitname Unit name. Can also be passed as UNIT object.
-- @return Ops.OpsGroup#OPSGROUP OPS group object.
function DATABASE:FindOpsGroupFromUnit(unitname)
local unit=nil --Wrapper.Unit#UNIT
local groupname
-- Get group and group name.
if type(unitname)=="string" then
unit=UNIT:FindByName(unitname)
else
unit=unitname
end
if unit then
groupname=unit:GetGroup():GetName()
end
if groupname then
return self.FLIGHTGROUPS[groupname]
else
return nil
end
end
--- Add a flight control to the data base.
-- @param #DATABASE self
-- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol

View File

@ -6331,7 +6331,7 @@ do -- SET_OPSGROUP
--- Creates a new SET_OPSGROUP object, building a set of groups belonging to a coalitions, categories, countries, types or with defined prefix names.
-- @param #SET_OPSGROUP self
-- @return #SET_OPSGROUP
-- @return #SET_OPSGROUP self
function SET_OPSGROUP:New()
-- Inherit SET_BASE.
@ -6413,6 +6413,14 @@ do -- SET_OPSGROUP
-- Trigger Added event.
self:Added(ObjectName, object)
end
--- Adds a @{Core.Base#BASE} object in the @{Core.Set#SET_BASE}, using the Object Name as the index.
-- @param #SET_BASE self
-- @param Ops.OpsGroup#OPSGROUP Object Ops group
-- @return Core.Base#BASE The added BASE Object.
function SET_OPSGROUP:AddObject(Object)
self:Add(Object.groupname, Object)
end
--- Add a GROUP or OPSGROUP object to the set.

View File

@ -2667,6 +2667,26 @@ function RANGE:_DisplayRangeInfo( _unitname )
text = text .. string.format( "Max strafing alt AGL: %s\n", tstrafemaxalt )
text = text .. string.format( "# of strafe targets: %d\n", self.nstrafetargets )
text = text .. string.format( "# of bomb targets: %d\n", self.nbombtargets )
if self.instructor then
local alive = "N/A"
if self.instructorrelayname then
local relay = UNIT:FindByName( self.instructorrelayname )
if relay then
alive = tostring( relay:IsAlive() )
end
end
text = text .. string.format( "Instructor %.3f MHz (Relay=%s)\n", self.instructorfreq, alive )
end
if self.rangecontrol then
local alive = "N/A"
if self.rangecontrolrelayname then
local relay = UNIT:FindByName( self.rangecontrolrelayname )
if relay then
alive = tostring( relay:IsAlive() )
end
end
text = text .. string.format( "Control %.3f MHz (Relay=%s)\n", self.rangecontrolfreq, alive )
end
text = text .. texthit
text = text .. textbomb
text = text .. textdelay

View File

@ -349,6 +349,7 @@
-- * @{#WAREHOUSE.Attribute.GROUND_APC} Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets.
-- * @{#WAREHOUSE.Attribute.GROUND_TRUCK} Unarmed ground vehicles, which has the DCS "Truck" attribute.
-- * @{#WAREHOUSE.Attribute.GROUND_INFANTRY} Ground infantry assets.
-- * @{#WAREHOUSE.Attribute.GROUND_IFV} Ground infantry fighting vehicle.
-- * @{#WAREHOUSE.Attribute.GROUND_ARTILLERY} Artillery assets.
-- * @{#WAREHOUSE.Attribute.GROUND_TANK} Tanks (modern or old).
-- * @{#WAREHOUSE.Attribute.GROUND_TRAIN} Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently.
@ -1704,6 +1705,7 @@ WAREHOUSE.Descriptor = {
-- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets.
-- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute.
-- @field #string GROUND_INFANTRY Ground infantry assets.
-- @field #string GROUND_IFV Ground infantry fighting vehicle.
-- @field #string GROUND_ARTILLERY Artillery assets.
-- @field #string GROUND_TANK Tanks (modern or old).
-- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently.
@ -1730,6 +1732,7 @@ WAREHOUSE.Attribute = {
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
@ -1936,6 +1939,7 @@ function WAREHOUSE:New(warehouse, alias)
self:SetMarker(true)
self:SetReportOff()
self:SetRunwayRepairtime()
self.allowSpawnOnClientSpots=false
-- Add warehouse to database.
_WAREHOUSEDB.Warehouses[self.uid]=self
@ -2581,6 +2585,14 @@ function WAREHOUSE:SetSafeParkingOff()
return self
end
--- Set wether client parking spots can be used for spawning.
-- @param #WAREHOUSE self
-- @return #WAREHOUSE self
function WAREHOUSE:SetAllowSpawnOnClientParking()
self.allowSpawnOnClientSpots=true
return self
end
--- Set low fuel threshold. If one unit of an asset has less fuel than this number, the event AssetLowFuel will be fired.
-- @param #WAREHOUSE self
-- @param #number threshold Relative low fuel threshold, i.e. a number in [0,1]. Default 0.15 (15%).
@ -5364,7 +5376,6 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param DCS#coalition.side Coalition Coalition side which originally captured the warehouse.
function WAREHOUSE:onafterRunwayDestroyed(From, Event, To)
-- Message.
@ -6311,10 +6322,11 @@ function WAREHOUSE:_RouteAir(aircraft)
self:T2(self.lid..string.format("RouteAir aircraft group %s alive=%s", aircraft:GetName(), tostring(aircraft:IsAlive())))
-- Give start command to activate uncontrolled aircraft within the next 60 seconds.
if not self.flightcontrol then
local starttime=math.random(60)
aircraft:StartUncontrolled(starttime)
if self.flightcontrol then
local fg=FLIGHTGROUP:New(aircraft)
fg:SetReadyForTakeoff(true)
else
aircraft:StartUncontrolled(math.random(60))
end
-- Debug info.
@ -7874,14 +7886,16 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets)
-- Get client coordinates.
local function _clients()
local clients=_DATABASE.CLIENTS
local coords={}
for clientname, client in pairs(clients) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units) do
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
coords[unit.name]=coord
if not self.allowSpawnOnClientSpots then
local clients=_DATABASE.CLIENTS
for clientname, client in pairs(clients) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units) do
local coord=COORDINATE:New(unit.x, unit.alt, unit.y)
coords[unit.name]=coord
end
end
end
return coords
@ -8351,9 +8365,10 @@ function WAREHOUSE:_GetAttribute(group)
--- Ground ---
--------------
-- Ground
local apc=group:HasAttribute("Infantry carriers")
local apc=group:HasAttribute("APC") --("Infantry carriers")
local truck=group:HasAttribute("Trucks") and group:GetCategory()==Group.Category.GROUND
local infantry=group:HasAttribute("Infantry")
local ifv=group:HasAttribute("IFV")
local artillery=group:HasAttribute("Artillery")
local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks")
local aaa=group:HasAttribute("AAA")
@ -8390,6 +8405,8 @@ function WAREHOUSE:_GetAttribute(group)
attribute=WAREHOUSE.Attribute.AIR_UAV
elseif apc then
attribute=WAREHOUSE.Attribute.GROUND_APC
elseif ifv then
attribute=WAREHOUSE.Attribute.GROUND_IFV
elseif infantry then
attribute=WAREHOUSE.Attribute.GROUND_INFANTRY
elseif artillery then

View File

@ -31,6 +31,7 @@ __Moose.Include( 'Scripts/Moose/Core/Spot.lua' )
__Moose.Include( 'Scripts/Moose/Core/Astar.lua' )
__Moose.Include( 'Scripts/Moose/Core/MarkerOps_Base.lua' )
__Moose.Include( 'Scripts/Moose/Core/TextAndSound.lua' )
__Moose.Include( 'Scripts/Moose/Core/Condition.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Object.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Identifiable.lua' )
@ -101,6 +102,8 @@ __Moose.Include( 'Scripts/Moose/Ops/Chief.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Flotilla.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Fleet.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Awacs.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Operation.lua' )
__Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )

View File

@ -52,7 +52,6 @@
--- ATIS class.
-- @type ATIS
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string theatre DCS map name.
-- @field #string airbasename The name of the airbase.
@ -309,7 +308,6 @@
-- @field #ATIS
ATIS = {
ClassName = "ATIS",
Debug = false,
lid = nil,
theatre = nil,
airbasename = nil,
@ -614,26 +612,26 @@ ATIS.version="0.9.6"
--- Create a new ATIS class object for a specific aircraft carrier unit.
-- @param #ATIS self
-- @param #string airbasename Name of the airbase.
-- @param #number frequency Radio frequency in MHz. Default 143.00 MHz.
-- @param #number modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators
-- @param #string AirbaseName Name of the airbase.
-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz.
-- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators.
-- @return #ATIS self
function ATIS:New(airbasename, frequency, modulation)
function ATIS:New(AirbaseName, Frequency, Modulation)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #ATIS
self.airbasename=airbasename
self.airbase=AIRBASE:FindByName(airbasename)
self.airbasename=AirbaseName
self.airbase=AIRBASE:FindByName(AirbaseName)
if self.airbase==nil then
self:E("ERROR: Airbase %s for ATIS could not be found!", tostring(airbasename))
self:E("ERROR: Airbase %s for ATIS could not be found!", tostring(AirbaseName))
return nil
end
-- Default freq and modulation.
self.frequency=frequency or 143.00
self.modulation=modulation or 0
self.frequency=Frequency or 143.00
self.modulation=Modulation or 0
-- Get map.
self.theatre=env.mission.theatre
@ -740,15 +738,6 @@ function ATIS:New(airbasename, frequency, modulation)
-- @param #string To To state.
-- @param #string Text Report text.
-- Debug trace.
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
return self
end
@ -809,6 +798,15 @@ function ATIS:SetRunwayLength()
return self
end
--- Give information on runway length.
-- @param #ATIS self
-- @return #ATIS self
function ATIS:SetRunwayLength()
self.rwylength=true
return self
end
--- Give information on airfield elevation
-- @param #ATIS self
-- @return #ATIS self
@ -1137,15 +1135,19 @@ end
-- @param #number Port SRS port. Default 5002.
-- @return #ATIS self
function ATIS:SetSRS(PathToSRS, Gender, Culture, Voice, Port)
self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
self.msrs:SetPort(Port)
self.msrs:SetCoalition(self:GetCoalition())
if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90)
if PathToSRS then
self.useSRS=true
self.msrs=MSRS:New(PathToSRS, self.frequency, self.modulation)
self.msrs:SetGender(Gender)
self.msrs:SetCulture(Culture)
self.msrs:SetVoice(Voice)
self.msrs:SetPort(Port)
self.msrs:SetCoalition(self:GetCoalition())
if self.dTQueueCheck<=10 then
self:SetQueueUpdateTime(90)
end
else
self:E(self.lid..string.format("ERROR: No SRS path specified!"))
end
return self
end
@ -1391,7 +1393,8 @@ function ATIS:onafterBroadcast(From, Event, To)
--- Runway ---
--------------
local runway, rwyLeft=self:GetActiveRunway()
local runwayLanding, rwyLandingLeft=self:GetActiveRunway()
local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true)
------------
--- Time ---
@ -2017,19 +2020,19 @@ function ATIS:onafterBroadcast(From, Event, To)
alltext=alltext..";\n"..subtitle
-- Active runway.
local subtitle=string.format("Active runway %s", runway)
if rwyLeft==true then
local subtitle=string.format("Active runway %s", runwayLanding)
if rwyLandingLeft==true then
subtitle=subtitle.." Left"
elseif rwyLeft==false then
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(runway)
if rwyLeft==true then
self.radioqueue:Number2Transmission(runwayLanding)
if rwyLandingLeft==true then
self:Transmission(ATIS.Sound.Left, 0.2)
elseif rwyLeft==false then
elseif rwyLandingLeft==false then
self:Transmission(ATIS.Sound.Right, 0.2)
end
end
@ -2141,7 +2144,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end
-- ILS
local ils=self:GetNavPoint(self.ils, runway, rwyLeft)
local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft)
if ils then
subtitle=string.format("ILS frequency %.2f MHz", ils.frequency)
if not self.useSRS then
@ -2159,7 +2162,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end
-- Outer NDB
local ndb=self:GetNavPoint(self.ndbouter, runway, rwyLeft)
local ndb=self:GetNavPoint(self.ndbouter, runwayLanding, rwyLandingLeft)
if ndb then
subtitle=string.format("Outer NDB frequency %.2f MHz", ndb.frequency)
if not self.useSRS then
@ -2177,7 +2180,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end
-- Inner NDB
local ndb=self:GetNavPoint(self.ndbinner, runway, rwyLeft)
local ndb=self:GetNavPoint(self.ndbinner, runwayLanding, rwyLandingLeft)
if ndb then
subtitle=string.format("Inner NDB frequency %.2f MHz", ndb.frequency)
if not self.useSRS then
@ -2236,7 +2239,7 @@ function ATIS:onafterBroadcast(From, Event, To)
end
-- PRMG
local ndb=self:GetNavPoint(self.prmg, runway, rwyLeft)
local ndb=self:GetNavPoint(self.prmg, runwayLanding, rwyLandingLeft)
if ndb then
subtitle=string.format("PRMG channel %d", ndb.frequency)
if not self.useSRS then
@ -2363,39 +2366,19 @@ end
--- Get active runway runway.
-- @param #ATIS self
-- @param #boolean Takeoff If `true`, get runway for takeoff. Default is for landing.
-- @return #string Active runway, e.g. "31" for 310 deg.
-- @return #boolean Use Left=true, Right=false, or nil.
function ATIS:GetActiveRunway()
local coord=self.airbase:GetCoordinate()
local height=coord:GetLandHeight()
-- Get wind direction and speed in m/s.
local windFrom, windSpeed=coord:GetWind(height+10)
-- Get active runway data based on wind direction.
local runact=self.airbase:GetActiveRunway(self.runwaym2t)
-- Active runway "31".
local runway=self:GetMagneticRunway(windFrom) or runact.idx
-- Left or right in case there are two runways with the same heading.
local rwyLeft=nil
-- Check if user explicitly specified a runway.
if self.activerunway then
-- Get explicit runway heading if specified.
local runwayno=self:GetRunwayWithoutLR(self.activerunway)
if runwayno~="" then
runway=runwayno
end
-- Was "L"eft or "R"ight given?
rwyLeft=self:GetRunwayLR(self.activerunway)
function ATIS:GetActiveRunway(Takeoff)
local runway=nil --Wrapper.Airbase#AIRBASE.Runway
if Takeoff then
runway=self.airbase:GetActiveRunwayTakeoff()
else
runway=self.airbase:GetActiveRunwayLanding()
end
return runway, rwyLeft
return runway.name, runway.isLeft
end
--- Get runway from user supplied magnetic heading.

View File

@ -6059,7 +6059,7 @@ function AIRBOSS:_MarshalAI( flight, nstack, respawn )
local radial = self:GetRadial( case, false, true )
-- Point in the middle of the race track and a 5 NM more port perpendicular.
p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90 ):Translate( UTILS.NMToMeters( 5 ), radial, true )
p0 = p2:Translate( UTILS.NMToMeters( 5 ), radial + 90, true ):Translate( UTILS.NMToMeters( 5 ), radial, true )
-- Entering Case II/III marshal pattern waypoint.
wp[#wp + 1] = p0:WaypointAirTurningPoint( nil, speedTransit, { TaskArrivedHolding }, "Entering Case II/III Marshal Pattern" )

View File

@ -34,6 +34,11 @@
-- @field #boolean isMobile If true, group is mobile.
-- @field #ARMYGROUP.Target engage Engage target.
-- @field Core.Set#SET_ZONE retreatZones Set of retreat zones.
-- @field #boolean suppressOn Bla
-- @field #boolean isSuppressed Bla
-- @field #number TsuppressMin Bla
-- @field #number TsuppressMax Bla
-- @field #number TsuppressAve Bla
-- @extends Ops.OpsGroup#OPSGROUP
--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B Sledge
@ -63,7 +68,7 @@ ARMYGROUP = {
--- Army Group version.
-- @field #string version
ARMYGROUP.version="0.7.3"
ARMYGROUP.version="0.7.9"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -122,6 +127,9 @@ function ARMYGROUP:New(group)
self:AddTransition("*", "Retreat", "Retreating") -- Order a retreat.
self:AddTransition("Retreating", "Retreated", "Retreated") -- Group retreated.
self:AddTransition("*", "Suppressed", "*") -- Group is suppressed
self:AddTransition("*", "Unsuppressed", "*") -- Group is unsuppressed.
self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target from Cruising state
self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target from Holding state
self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target from OnDetour state
@ -129,7 +137,7 @@ function ARMYGROUP:New(group)
self:AddTransition("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled.
self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed.
self:AddTransition("Rearming", "Rearmed", "Cruising") -- Group was rearmed.
self:AddTransition("*", "Rearmed", "Cruising") -- Group was rearmed.
------------------------
--- Pseudo Functions ---
@ -280,6 +288,7 @@ function ARMYGROUP:New(group)
-- @function [parent=#ARMYGROUP] EngageTarget
-- @param #ARMYGROUP self
-- @param Wrapper.Group#GROUP Group the group to be engaged.
-- @param #number Speed Speed in knots.
-- @param #string Formation Formation used in the engagement.
--- Triggers the FSM event "EngageTarget" after a delay.
@ -287,6 +296,7 @@ function ARMYGROUP:New(group)
-- @param #ARMYGROUP self
-- @param #number delay Delay in seconds.
-- @param Wrapper.Group#GROUP Group the group to be engaged.
-- @param #number Speed Speed in knots.
-- @param #string Formation Formation used in the engagement.
@ -297,6 +307,7 @@ function ARMYGROUP:New(group)
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Group#GROUP Group the group to be engaged.
-- @param #number Speed Speed in knots.
-- @param #string Formation Formation used in the engagement.
@ -386,7 +397,7 @@ function ARMYGROUP:New(group)
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
self:HandleEvent(EVENTS.Dead, self.OnEventDead)
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
--self:HandleEvent(EVENTS.Hit, self.OnEventHit)
self:HandleEvent(EVENTS.Hit, self.OnEventHit)
-- Start the status monitoring.
self.timerStatus=TIMER:New(self.Status, self):Start(1, 30)
@ -572,6 +583,47 @@ function ARMYGROUP:AddRetreatZone(RetreatZone)
return self
end
--- Set suppression on. average, minimum and maximum time a unit is suppressed each time it gets hit.
-- @param #ARMYGROUP self
-- @param #number Tave Average time [seconds] a group will be suppressed. Default is 15 seconds.
-- @param #number Tmin (Optional) Minimum time [seconds] a group will be suppressed. Default is 5 seconds.
-- @param #number Tmax (Optional) Maximum time a group will be suppressed. Default is 25 seconds.
-- @return #ARMYGROUP self
function ARMYGROUP:SetSuppressionOn(Tave, Tmin, Tmax)
-- Activate suppression.
self.suppressionOn=true
-- Minimum suppression time is input or default 5 sec (but at least 1 second).
self.TsuppressMin=Tmin or 1
self.TsuppressMin=math.max(self.TsuppressMin, 1)
-- Maximum suppression time is input or default but at least Tmin.
self.TsuppressMax=Tmax or 15
self.TsuppressMax=math.max(self.TsuppressMax, self.TsuppressMin)
-- Expected suppression time is input or default but at leat Tmin and at most Tmax.
self.TsuppressAve=Tave or 10
self.TsuppressAve=math.max(self.TsuppressMin)
self.TsuppressAve=math.min(self.TsuppressMax)
-- Debug Info
self:T(self.lid..string.format("Set ave suppression time to %d seconds.", self.TsuppressAve))
self:T(self.lid..string.format("Set min suppression time to %d seconds.", self.TsuppressMin))
self:T(self.lid..string.format("Set max suppression time to %d seconds.", self.TsuppressMax))
return self
end
--- Set suppression off.
-- @param #ARMYGROUP self
-- @return #ARMYGROUP self
function ARMYGROUP:SetSuppressionOff()
-- Activate suppression.
self.suppressionOn=false
end
--- Check if the group is currently holding its positon.
-- @param #ARMYGROUP self
-- @return #boolean If true, group was ordered to hold.
@ -651,10 +703,14 @@ function ARMYGROUP:Status()
-- Check if group is waiting.
if self:IsWaiting() then
if self.Twaiting and self.dTwait then
if timer.getAbsTime()>self.Twaiting+self.dTwait then
if timer.getAbsTime()>self.Twaiting+self.dTwait then
self.Twaiting=nil
self.dTwait=nil
self:Cruise()
if self:_CountPausedMissions()>0 then
self:UnpauseMission()
else
self:Cruise()
end
end
end
end
@ -799,22 +855,6 @@ end
-- DCS Events ==> See OPSGROUP
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Event function handling when a unit is hit.
-- @param #ARMYGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function ARMYGROUP:OnEventHit(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- TODO: suppression
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -880,6 +920,12 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
-- Set default EPLRS.
self:SwitchEPLRS(self.option.EPLRS)
-- Set default Invisible.
self:SwitchInvisible(self.option.Invisible)
-- Set default Immortal.
self:SwitchImmortal(self.option.Immortal)
-- Set TACAN to default.
self:_SwitchTACAN()
@ -943,6 +989,9 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation)
elseif self:IsHolding() then
self:T(self.lid.."Update route denied. Group is holding position!")
return false
elseif self:IsEngaging() then
self:T(self.lid.."Update route allowed. Group is engaging!")
return true
end
-- Check for a current task.
@ -960,7 +1009,7 @@ function ARMYGROUP:onbeforeUpdateRoute(From, Event, To, n, N, Speed, Formation)
self:T2(self.lid.."Allowing update route for Task: ReconMission")
elseif task.dcstask.id==AUFTRAG.SpecialTask.RELOCATECOHORT then
-- For relocate
self:T2(self.lid.."Allowing update route for Task: Relocate Cohort")
self:T2(self.lid.."Allowing update route for Task: Relocate Cohort")
else
local taskname=task and task.description or "No description"
self:T(self.lid..string.format("WARNING: Update route denied because taskcurrent=%d>0! Task description = %s", self.taskcurrent, tostring(taskname)))
@ -1219,6 +1268,16 @@ end
function ARMYGROUP:onafterOutOfAmmo(From, Event, To)
self:T(self.lid..string.format("Group is out of ammo at t=%.3f", timer.getTime()))
-- Get current task.
local task=self:GetTaskCurrent()
if task then
if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then
self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id))
self:TaskCancel(task)
end
end
-- Fist, check if we want to rearm once out-of-ammo.
--TODO: IsMobile() check
if self.rearmOnOutOfAmmo then
@ -1241,16 +1300,6 @@ function ARMYGROUP:onafterOutOfAmmo(From, Event, To)
if self.rtzOnOutOfAmmo then
self:__RTZ(-1)
end
-- Get current task.
local task=self:GetTaskCurrent()
if task then
if task.dcstask.id=="FireAtPoint" or task.dcstask.id==AUFTRAG.SpecialTask.BARRAGE then
self:T(self.lid..string.format("Cancelling current %s task because out of ammo!", task.dcstask.id))
self:TaskCancel(task)
end
end
end
@ -1283,6 +1332,15 @@ function ARMYGROUP:onbeforeRearm(From, Event, To, Coordinate, Formation)
allowed=false
end
-- Check if coordinate is provided.
if allowed and not Coordinate then
local truck=self:FindNearestAmmoSupply()
if truck and truck:IsAlive() then
self:__Rearm(-0.1, truck:GetCoordinate(), Formation)
end
return false
end
-- Try again...
if dt then
self:T(self.lid..string.format("Trying Rearm again in %.2f sec", dt))
@ -1323,9 +1381,22 @@ end
-- @param #string To To state.
function ARMYGROUP:onafterRearmed(From, Event, To)
self:T(self.lid.."Group rearmed")
-- Get Current mission.
local mission=self:GetMissionCurrent()
-- Check if this is a rearming mission.
if mission and mission.type==AUFTRAG.Type.REARMING then
-- Rearmed ==> Mission Done! This also checks if the group is done.
self:MissionDone(mission)
else
-- Check group done.
self:_CheckGroupDone(1)
end
-- Check group done.
self:_CheckGroupDone(1)
end
--- On before "RTZ" event.
@ -1580,7 +1651,7 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target, Speed, Formation
self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate())
-- Get a coordinate close to the target.
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.9)
local intercoord=self:GetCoordinate():GetIntermediateCoordinate(self.engage.Coordinate, 0.95)
-- Backup ROE and alarm state.
self.engage.roe=self:GetROE()
@ -1743,6 +1814,21 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation)
end
--- On after "Hit" event.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Wrapper.Unit#UNIT Enemy Unit that hit the element or `nil`.
function ARMYGROUP:onafterHit(From, Event, To, Enemy)
self:T(self.lid..string.format("ArmyGroup hit by %s", Enemy and Enemy:GetName() or "unknown"))
if self.suppressionOn then
env.info(self.lid.."FF suppress")
self:_Suppress()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Routing
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1987,6 +2073,100 @@ function ARMYGROUP:FindNearestAmmoSupply(Radius)
return nil, nil
end
--- Suppress fire of the group by setting its ROE to weapon hold.
-- @param #ARMYGROUP self
function ARMYGROUP:_Suppress()
-- Current time.
local Tnow=timer.getTime()
-- Current ROE
local currROE=self:GetROE()
-- Get randomized time the unit is suppressed.
local sigma=(self.TsuppressMax-self.TsuppressMin)/4
-- Gaussian distribution.
local Tsuppress=UTILS.RandomGaussian(self.TsuppressAve,sigma,self.TsuppressMin, self.TsuppressMax)
-- Time at which the suppression is over.
local renew=true
if not self.TsuppressionOver then
-- Group is not suppressed currently.
self.TsuppressionOver=Tnow+Tsuppress
-- Group will hold their weapons.
self:SwitchROE(ENUMS.ROE.WeaponHold)
-- Backup ROE.
self.suppressionROE=currROE
else
-- Check if suppression is longer than current time.
if Tsuppress+Tnow > self.TsuppressionOver then
self.TsuppressionOver=Tnow+Tsuppress
else
renew=false
end
end
-- Recovery event will be called in Tsuppress seconds.
if renew then
self:__Unsuppressed(self.TsuppressionOver-Tnow)
end
-- Debug message.
self:T(self.lid..string.format("Suppressed for %d sec", Tsuppress))
end
--- Before "Recovered" event. Check if suppression time is over.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @return #boolean
function ARMYGROUP:onbeforeUnsuppressed(From, Event, To)
-- Current time.
local Tnow=timer.getTime()
-- Debug info
self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver))
-- Recovery is only possible if enough time since the last hit has passed.
if Tnow >= self.TsuppressionOver then
return true
else
return false
end
end
--- After "Recovered" event. Group has recovered and its ROE is set back to the "normal" unsuppressed state. Optionally the group is flared green.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function ARMYGROUP:onafterUnsuppressed(From, Event, To)
-- Debug message.
local text=string.format("Group %s has recovered!", self:GetName())
MESSAGE:New(text, 10):ToAll()
self:T(self.lid..text)
-- Set ROE back to default.
self:SwitchROE(self.suppressionROE)
-- Flare unit green.
if true then
self.group:FlareGreen()
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -72,6 +72,8 @@
--
-- @field Ops.Target#TARGET engageTarget Target data to engage.
--
-- @field Ops.Operation#OPERATION operation Operation this mission is part of.
--
-- @field #boolean teleport Groups are teleported to the mission ingress waypoint.
--
-- @field Core.Zone#ZONE_RADIUS engageZone *Circular* engagement zone.
@ -111,6 +113,9 @@
-- @field #number artyAngle Shooting angle in degrees (for Barrage).
--
-- @field #string alert5MissionType Alert 5 mission type. This is the mission type, the alerted assets will be able to carry out.
--
-- @field #table attributes Generalized attribute(s) of assets.
-- @field #table properties DCS attribute(s) of assets.
--
-- @field Ops.Chief#CHIEF chief The CHIEF managing this mission.
-- @field Ops.Commander#COMMANDER commander The COMMANDER managing this mission.
@ -168,6 +173,8 @@
-- @field #number optionRTBfuel RTB on out-of-fuel.
-- @field #number optionECM ECM.
-- @field #boolean optionEmission Emission is on or off.
-- @field #boolean optionInvisible Invisible is on/off.
-- @field #boolean optionImmortal Immortal is on/off.
--
-- @extends Core.Fsm#FSM
@ -411,6 +418,7 @@ _AUFTRAGSNR=0
-- @field #string AIRDEFENSE Air defense.
-- @field #string EWR Early Warning Radar.
-- @field #string RECOVERYTANKER Recovery tanker.
-- @filed #string REARMING Rearming mission.
-- @field #string NOTHING Nothing.
AUFTRAG.Type={
ANTISHIP="Anti Ship",
@ -452,6 +460,7 @@ AUFTRAG.Type={
AIRDEFENSE="Air Defence",
EWR="Early Warning Radar",
RECOVERYTANKER="Recovery Tanker",
REARMING="Rearming",
NOTHING="Nothing",
}
@ -473,6 +482,7 @@ AUFTRAG.Type={
-- @field #string AIRDEFENSE Air defense.
-- @field #string EWR Early Warning Radar.
-- @field #string RECOVERYTANKER Recovery tanker.
-- @field #string REARMING Rearming.
-- @field #string NOTHING Nothing.
AUFTRAG.SpecialTask={
FORMATION="Formation",
@ -492,6 +502,7 @@ AUFTRAG.SpecialTask={
AIRDEFENSE="Air Defense",
EWR="Early Warning Radar",
RECOVERYTANKER="Recovery Tanker",
REARMING="Rearming",
NOTHING="Nothing",
}
@ -687,6 +698,9 @@ function AUFTRAG:New(Type)
self.Ncasualties=0
self.Nkills=0
self.Nelements=0
self.Ngroups=0
self.Nassigned=nil
self.Ndead=0
-- FMS start state is PLANNED.
self:SetStartState(self.status)
@ -1763,7 +1777,7 @@ end
--- **[GROUND, NAVAL]** Create an ARTY mission.
-- @param #AUFTRAG self
-- @param Core.Point#COORDINATE Target Center of the firing solution.
-- @param #number Nshots Number of shots to be fired. Default 3.
-- @param #number Nshots Number of shots to be fired. Default `#nil`.
-- @param #number Radius Radius of the shells in meters. Default 100 meters.
-- @param #number Altitude Altitude in meters. Can be used to setup a Barrage. Default `#nil`.
-- @return #AUFTRAG self
@ -1999,6 +2013,30 @@ function AUFTRAG:NewFUELSUPPLY(Zone)
return mission
end
--- **[GROUND]** Create a REARMING mission.
-- @param #AUFTRAG self
-- @param Core.Zone#ZONE Zone The zone, where units go and look for ammo supply.
-- @return #AUFTRAG self
function AUFTRAG:NewREARMING(Zone)
local mission=AUFTRAG:New(AUFTRAG.Type.REARMING)
mission:_TargetFromObject(Zone)
mission.optionROE=ENUMS.ROE.WeaponHold
mission.optionAlarm=ENUMS.AlarmState.Auto
mission.missionFraction=1.0
mission.missionWaypointRadius=0
mission.categories={AUFTRAG.Category.GROUND}
mission.DCStask=mission:GetDCSMissionTask()
return mission
end
--- **[AIR]** Create an ALERT 5 mission. Aircraft will be spawned uncontrolled and wait for an assignment. You must specify **one** mission type which is performed.
-- This determines the payload and the DCS mission task which are used when the aircraft is spawned.
@ -2196,7 +2234,9 @@ function AUFTRAG:NewFromTarget(Target, MissionType)
elseif MissionType==AUFTRAG.Type.STRIKE then
mission=self:NewSTRIKE(Target, Altitude)
elseif MissionType==AUFTRAG.Type.ARMORATTACK then
mission=self:NewARMORATTACK(Target,Speed)
mission=self:NewARMORATTACK(Target, Speed)
elseif MissionType==AUFTRAG.Type.GROUNDATTACK then
mission=self:NewGROUNDATTACK(Target, Speed, Formation)
else
return nil
end
@ -2414,9 +2454,13 @@ end
--- Set that mission assets are teleported to the mission execution waypoint.
-- @param #AUFTRAG self
-- @param #boolean Switch If `true` or `nil`, teleporting is on. If `false`, teleporting is off.
-- @return #AUFTRAG self
function AUFTRAG:SetTeleport()
self.teleport=true
function AUFTRAG:SetTeleport(Switch)
if Switch==nil then
Switch=true
end
self.teleport=Switch
return self
end
@ -2522,6 +2566,21 @@ function AUFTRAG:GetRequiredAssets(Legion)
return Nmin, Nmax
end
--- **[LEGION, COMMANDER, CHIEF]** Set that only alive (spawned) assets are considered.
-- @param #AUFTRAG self
-- @param #boolean Switch If true or nil, only active assets. If false
-- @return #AUFTRAG self
function AUFTRAG:SetAssetsStayAlive(Switch)
if Switch==nil then
Switch=true
end
self.assetStayAlive=Switch
return self
end
--- **[LEGION, COMMANDER, CHIEF]** Define how many assets are required that escort the mission assets.
-- Only used if the mission is handled by a **LEGION** (AIRWING, BRIGADE, FLEET) or higher level.
-- @param #AUFTRAG self
@ -2970,6 +3029,36 @@ function AUFTRAG:SetEmission(OnOffSwitch)
return self
end
--- Set invisibility setting for this mission.
-- @param #AUFTRAG self
-- @param #boolean OnOffSwitch If `true` or `nil`, invisible is on. If `false`, invisible is off.
-- @return #AUFTRAG self
function AUFTRAG:SetInvisible(OnOffSwitch)
if OnOffSwitch==nil then
self.optionInvisible=true
else
self.optionInvisible=OnOffSwitch
end
return self
end
--- Set immortality setting for this mission.
-- @param #AUFTRAG self
-- @param #boolean OnOffSwitch If `true` or `nil`, immortal is on. If `false`, immortal is off.
-- @return #AUFTRAG self
function AUFTRAG:SetImmortal(OnOffSwitch)
if OnOffSwitch==nil then
self.optionImmortal=true
else
self.optionImmortal=OnOffSwitch
end
return self
end
--- Set formation for this mission.
-- @param #AUFTRAG self
-- @param #number Formation Formation.
@ -3288,9 +3377,33 @@ end
--- Check if mission is EXECUTING. The first OPSGROUP has reached the mission execution waypoint and is not executing the mission task.
-- @param #AUFTRAG self
-- @param #boolean AllGroups (Optional) Check that all groups are currently executing the mission.
-- @return #boolean If true, mission is currently executing.
function AUFTRAG:IsExecuting()
return self.status==AUFTRAG.Status.EXECUTING
function AUFTRAG:IsExecuting(AllGroups)
local isExecuting=self.status==AUFTRAG.Status.EXECUTING
if AllGroups and isExecuting then
-- Number of groups executing.
local n=self:CountOpsGroupsInStatus(AUFTRAG.GroupStatus.EXECUTING)
local N
if self.Nassigned then
N=self.Nassigned-self.Ndead
else
N=self:CountOpsGroups()
end
if n==N then
return true
else
return false
end
end
return isExecuting
end
--- Check if mission was cancelled.
@ -4091,7 +4204,7 @@ function AUFTRAG:CheckGroupsDone()
if groupdata then
if not (groupdata.status==AUFTRAG.GroupStatus.DONE or groupdata.status==AUFTRAG.GroupStatus.CANCELLED) then
-- At least this flight is not DONE or CANCELLED.
self:T(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status))
self:T2(self.lid..string.format("CheckGroupsDone: OPSGROUP %s is not DONE or CANCELLED but in state %s. Mission NOT DONE!", groupdata.opsgroup.groupname, groupdata.status:upper()))
return false
end
end
@ -4103,7 +4216,7 @@ function AUFTRAG:CheckGroupsDone()
local status=self:GetLegionStatus(legion)
if not status==AUFTRAG.Status.CANCELLED then
-- At least one LEGION has not CANCELLED.
self:T(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!", legion.alias, status))
self:T2(self.lid..string.format("CheckGroupsDone: LEGION %s is not CANCELLED but in state %s. Mission NOT DONE!", legion.alias, status))
return false
end
end
@ -4111,7 +4224,7 @@ function AUFTRAG:CheckGroupsDone()
-- Check commander status.
if self.commander then
if not self.statusCommander==AUFTRAG.Status.CANCELLED then
self:T(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!", self.statusCommander))
self:T2(self.lid..string.format("CheckGroupsDone: COMMANDER is not CANCELLED but in state %s. Mission NOT DONE!", self.statusCommander))
return false
end
end
@ -4119,14 +4232,14 @@ function AUFTRAG:CheckGroupsDone()
-- Check chief status.
if self.chief then
if not self.statusChief==AUFTRAG.Status.CANCELLED then
self:T(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!", self.statusChief))
self:T2(self.lid..string.format("CheckGroupsDone: CHIEF is not CANCELLED but in state %s. Mission NOT DONE!", self.statusChief))
return false
end
end
-- These are early stages, where we might not even have a opsgroup defined to be checked. If there were any groups, we checked above.
if self:IsPlanned() or self:IsQueued() or self:IsRequested() then
self:T(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState()))
self:T2(self.lid..string.format("CheckGroupsDone: Mission is still in state %s [FSM=%s] (PLANNED or QUEUED or REQUESTED). Mission NOT DONE!", self.status, self:GetState()))
return false
end
@ -4249,7 +4362,8 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group that is dead now.
-- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group to which the element belongs.
-- @param Ops.OpsGroup#OPSGROUP.Element Element The element that got destroyed.
function AUFTRAG:onafterElementDestroyed(From, Event, To, OpsGroup, Element)
-- Increase number of own casualties.
self.Ncasualties=self.Ncasualties+1
@ -4267,6 +4381,9 @@ function AUFTRAG:onafterGroupDead(From, Event, To, OpsGroup)
if asset then
self:AssetDead(asset)
end
-- Number of dead groups.
self.Ndead=self.Ndead+1
end
@ -4361,7 +4478,7 @@ function AUFTRAG:onafterCancel(From, Event, To)
else
-- Debug info.
self:T(self.lid..string.format("No legion, commander or chief. Attached flights will cancel the mission on their own. Will wait for mission DONE before evaluation!"))
self:T(self.lid..string.format("No legion, commander or chief. Attached groups will cancel the mission on their own. Will wait for mission DONE before evaluation!"))
-- Loop over all groups.
for _,_groupdata in pairs(self.groupdata or {}) do
@ -4625,6 +4742,9 @@ function AUFTRAG:onafterRepeat(From, Event, To)
-- Reset casualties and units assigned.
self.Ncasualties=0
self.Nelements=0
self.Ngroups=0
self.Nassigned=nil
self.Ndead=0
-- Update DCS mission task. Could be that the initial task (e.g. for bombing) was destroyed. Then we need to update the coordinate.
self.DCStask=self:GetDCSMissionTask()
@ -4897,12 +5017,18 @@ function AUFTRAG:AddAsset(Asset)
-- Add to table.
self.assets=self.assets or {}
-- Add to table.
table.insert(self.assets, Asset)
self.Nassigned=self.Nassigned or 0
self.Nassigned=self.Nassigned+1
return self
end
--- Add asset to mission.
--- Add assets to mission.
-- @param #AUFTRAG self
-- @param #table Assets List of assets.
-- @return #AUFTRAG self
@ -4967,6 +5093,22 @@ function AUFTRAG:CountOpsGroups()
return N
end
--- Count OPS groups in a certain status.
-- @param #AUFTRAG self
-- @param #string Status Status of group, e.g. `AUFTRAG.GroupStatus.EXECUTING`.
-- @return #number Number of alive OPS groups.
function AUFTRAG:CountOpsGroupsInStatus(Status)
local N=0
for _,_groupdata in pairs(self.groupdata) do
local groupdata=_groupdata --#AUFTRAG.GroupData
if groupdata and groupdata.status==Status then
N=N+1
end
end
return N
end
--- Get coordinate of target. First unit/group of the set is used.
-- @param #AUFTRAG self
@ -5140,6 +5282,10 @@ function AUFTRAG:GetDCSMissionTask()
-- ANTISHIP Mission --
----------------------
-- Add enroute anti-ship task.
local DCStask=CONTROLLABLE.EnRouteTaskAntiShip(nil)
table.insert(self.enrouteTasks, DCStask)
self:_GetDCSAttackTask(self.engageTarget, DCStasks)
elseif self.type==AUFTRAG.Type.AWACS then
@ -5478,8 +5624,6 @@ function AUFTRAG:GetDCSMissionTask()
end
--table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.BARRAGE then
---------------------
@ -5600,6 +5744,24 @@ function AUFTRAG:GetDCSMissionTask()
table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.AMMOSUPPLY then
----------------------
-- REARMING Mission --
----------------------
local DCStask={}
DCStask.id=AUFTRAG.SpecialTask.REARMING
-- We create a "fake" DCS task and pass the parameters to the OPSGROUP.
local param={}
param.zone=self:GetObjective()
DCStask.params=param
table.insert(DCStasks, DCStask)
elseif self.type==AUFTRAG.Type.ALERT5 then
---------------------

View File

@ -92,7 +92,15 @@ function BRIGADE:New(WarehouseName, BrigadeName)
-- Defaults
self:SetRetreatZones()
-- Turn ship into NAVYGROUP.
if self:IsShip() then
local wh=self.warehouse --Wrapper.Unit#UNIT
local group=wh:GetGroup()
self.warehouseOpsGroup=NAVYGROUP:New(group) --Ops.NavyGroup#NAVYGROUP
self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName())
end
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "ArmyOnMission", "*") -- An ARMYGROUP was send on a Mission (AUFTRAG).

View File

@ -120,6 +120,33 @@
--
-- Fleets can be added via the @{#CHIEF.AddFleet}() function.
--
-- ## Response on Target
--
-- When the chief detects a valid target, he will launch a certain number of selected assets. Only whole groups from SQUADRONs, PLATOONs or FLOTILLAs can be selected.
-- In other words, it is not possible to specify the abount of individual *units*.
--
-- By default, one group is selected for any detected target. This can, however, be customized with the @{CHIEF.SetResponseOnTarget}() function. The number of min and max
-- asset groups can be specified depending on threatlevel, category, mission type, number of units, defcon and strategy.
--
-- For example:
--
-- -- One group for aircraft targets of threat level 0 or higher.
-- myChief:SetResponseOnTarget(1, 1, 0, TARGET.Category.AIRCRAFT)
-- -- At least one and up to two groups for aircraft targets of threat level 8 or higher. This will overrule the previous response!
-- myChief:SetResponseOnTarget(1, 2, 8, TARGET.Category.AIRCRAFT)
--
-- -- At least one and up to three groups for ground targets of threat level 0 or higher if current strategy is aggressive.
-- myChief:SetResponseOnTarget(1, 1, 0, TARGET.Category.GROUND, nil ,nil, nil, CHIEF.Strategy.DEFENSIVE)
--
-- -- One group for BAI missions if current defcon is green.
-- myChief:SetResponseOnTarget(1, 1, 0, nil, AUFTRAG.Type.BAI, nil, CHIEF.DEFCON.GREEN)
--
-- -- At least one and up to four groups for BAI missions if current defcon is red.
-- myChief:SetResponseOnTarget(1, 2, 0, nil, AUFTRAG.Type.BAI, nil, CHIEF.DEFCON.YELLOW)
--
-- -- At least one and up to four groups for BAI missions if current defcon is red.
-- myChief:SetResponseOnTarget(1, 3, 0, nil, AUFTRAG.Type.BAI, nil, CHIEF.DEFCON.RED)
--
--
-- # Strategic (Capture) Zones
--
@ -256,6 +283,17 @@ CHIEF.Strategy = {
-- @field #string MissionType Mission Type.
-- @field #number Performance Performance: a number between 0 and 100, where 100 is best performance.
--- Asset numbers for detected targets.
-- @type CHIEF.AssetNumber
-- @field #number nAssetMin Min number of assets.
-- @field #number nAssetMax Max number of assets.
-- @field #number threatlevel Threat level.
-- @field #string targetCategory Target category.
-- @field #string missionType Mission type.
-- @field #number nUnits Number of enemy units.
-- @field #string defcon Defense condition.
-- @field #string strategy Strategy.
--- Strategic zone.
-- @type CHIEF.StrategicZone
-- @field Ops.OpsZone#OPSZONE opszone OPS zone.
@ -276,7 +314,7 @@ CHIEF.Strategy = {
--- CHIEF class version.
-- @field #string version
CHIEF.version="0.3.1"
CHIEF.version="0.4.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -773,6 +811,153 @@ function CHIEF:DeleteFromResource(Resource, MissionType)
return self
end
--- Set number of assets requested for detected targets.
-- @param #CHIEF self
-- @param #number NassetsMin Min number of assets. Should be at least 1. Default 1.
-- @param #number NassetsMax Max number of assets. Default is same as `NassetsMin`.
-- @param #number ThreatLevel Only apply this setting if the target threat level is greater or equal this number. Default 0.
-- @param #string TargetCategory Only apply this setting if the target is of this category, e.g. `TARGET.Category.AIRCRAFT`.
-- @param #string MissionType Only apply this setting for this mission type, e.g. `AUFTRAG.Type.INTERCEPT`.
-- @param #string Nunits Only apply this setting if the number of enemy units is greater or equal this number.
-- @param #string Defcon Only apply this setting if this defense condition is in place.
-- @param #string Strategy Only apply this setting if this strategy is in currently. place.
-- @return #CHIEF self
function CHIEF:SetResponseOnTarget(NassetsMin, NassetsMax, ThreatLevel, TargetCategory, MissionType, Nunits, Defcon, Strategy)
local bla={} --#CHIEF.AssetNumber
bla.nAssetMin=NassetsMin or 1
bla.nAssetMax=NassetsMax or bla.nAssetMin
bla.threatlevel=ThreatLevel or 0
bla.targetCategory=TargetCategory
bla.missionType=MissionType
bla.nUnits=Nunits or 1
bla.defcon=Defcon
bla.strategy=Strategy
self.assetNumbers=self.assetNumbers or {}
-- Add to table.
table.insert(self.assetNumbers, bla)
end
--- Add mission type and number of required assets to resource.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target The target.
-- @param #string MissionType Mission type.
-- @return #number Number of min assets.
-- @return #number Number of max assets.
function CHIEF:_GetAssetsForTarget(Target, MissionType)
-- Threat level.
local threatlevel=Target:GetThreatLevelMax()
-- Number of units.
local nUnits=Target.N0
-- Target category.
local targetcategory=Target:GetCategory()
-- Debug info.
self:T(self.lid..string.format("Getting number of assets for target with TL=%d, Category=%s, nUnits=%s, MissionType=%s", threatlevel, targetcategory, nUnits, tostring(MissionType)))
-- Candidates.
local candidates={}
local threatlevelMatch=nil
for _,_assetnumber in pairs(self.assetNumbers or {}) do
local assetnumber=_assetnumber --#CHIEF.AssetNumber
if (threatlevelMatch==nil and threatlevel>=assetnumber.threatlevel) or (threatlevelMatch~=nil and threatlevelMatch==threatlevel) then
if threatlevelMatch==nil then
threatlevelMatch=threatlevel
end
-- Number of other parameters matching.
local nMatch=0
-- Assume cand.
local cand=true
if assetnumber.targetCategory~=nil then
if assetnumber.targetCategory==targetcategory then
nMatch=nMatch+1
else
cand=false
end
end
if MissionType and assetnumber.missionType~=nil then
if assetnumber.missionType==MissionType then
nMatch=nMatch+1
else
cand=false
end
end
if assetnumber.nUnits~=nil then
if assetnumber.nUnits>=nUnits then
nMatch=nMatch+1
else
cand=false
end
end
if assetnumber.defcon~=nil then
if assetnumber.defcon==self.Defcon then
nMatch=nMatch+1
else
cand=false
end
end
if assetnumber.strategy~=nil then
if assetnumber.strategy==self.strategy then
nMatch=nMatch+1
else
cand=false
end
end
-- Add to candidates.
if cand then
table.insert(candidates, {assetnumber=assetnumber, nMatch=nMatch})
end
end
end
if #candidates>0 then
-- Return greater match.
local function _sort(a,b)
return a.nMatch>b.nMatch
end
-- Sort table by matches.
table.sort(candidates, _sort)
-- Pick the candidate with most matches.
local candidate=candidates[1]
-- Asset number.
local an=candidate.assetnumber --#CHIEF.AssetNumber
-- Debug message.
self:T(self.lid..string.format("Picking candidate with %d matches: NassetsMin=%d, NassetsMax=%d, ThreatLevel=%d, TargetCategory=%s, MissionType=%s, Defcon=%s, Strategy=%s",
candidate.nMatch, an.nAssetMin, an.nAssetMax, an.threatlevel, tostring(an.targetCategory), tostring(an.missionType), tostring(an.defcon), tostring(an.strategy)))
-- Return number of assetes.
return an.nAssetMin, an.nAssetMax
else
return 1, 1
end
end
--- Get defence condition.
-- @param #CHIEF self
-- @param #string Current Defence condition. See @{#CHIEF.DEFCON}, e.g. `CHIEF.DEFCON.RED`.
@ -955,6 +1140,7 @@ end
function CHIEF:AddTarget(Target)
if not self:IsTarget(Target) then
Target.chief=self
table.insert(self.targetqueue, Target)
end
@ -1536,7 +1722,7 @@ function CHIEF:onafterStatus(From, Event, To)
for _,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
if target and target:IsAlive() and target.mission and target.mission:IsNotOver() then
if target and target:IsAlive() and target.chief and target.mission and target.mission:IsNotOver() then
local inborder=self:CheckTargetInZones(target, self.borderzoneset)
@ -2083,24 +2269,6 @@ function CHIEF:CheckTargetQueue()
local Legions=nil
if #MissionPerformances>0 then
--TODO: Number of required assets. How many do we want? Should depend on:
-- * number of enemy units
-- * target threatlevel
-- * how many assets are still in stock
-- * is it inside of our border
-- * add damping factor
local NassetsMin=1
local NassetsMax=1
if threatlevel>=8 and target.N0 >=10 then
NassetsMax=3
elseif threatlevel>=5 then
NassetsMax=2
else
NassetsMax=1
end
for _,_mp in pairs(MissionPerformances) do
local mp=_mp --#CHIEF.MissionPerformance
@ -2111,12 +2279,15 @@ function CHIEF:CheckTargetQueue()
--env.info(string.format("FF chief %s nolimit=%s", mp.MissionType, tostring(NoLimit)))
if notlimited then
-- Get min/max number of assets.
local NassetsMin, NassetsMax=self:_GetAssetsForTarget(target, mp.MissionType)
-- Debug info.
self:T2(self.lid..string.format("Recruiting assets for mission type %s [performance=%d] of target %s", mp.MissionType, mp.Performance, target:GetName()))
-- Recruit assets.
local recruited, assets, legions=self:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax)
local recruited, assets, legions=self.commander:RecruitAssetsForTarget(target, mp.MissionType, NassetsMin, NassetsMax)
if recruited then
@ -2127,10 +2298,8 @@ function CHIEF:CheckTargetQueue()
-- Add asset to mission.
if mission then
for _,_asset in pairs(assets) do
local asset=_asset
mission:AddAsset(asset)
end
mission:_AddAssets(assets)
Legions=legions
-- We got what we wanted ==> leave loop.
@ -2650,45 +2819,6 @@ function CHIEF:_GetMissionTypeForGroupAttribute(Attribute)
return missionperf
end
--- Recruit assets for a given TARGET.
-- @param #CHIEF self
-- @param Ops.Target#TARGET Target The target.
-- @param #string MissionType Mission Type.
-- @param #number NassetsMin Min number of required assets.
-- @param #number NassetsMax Max number of required assets.
-- @return #boolean If `true` enough assets could be recruited.
-- @return #table Assets that have been recruited from all legions.
-- @return #table Legions that have recruited assets.
function CHIEF:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax)
-- Cohorts.
local Cohorts={}
for _,_legion in pairs(self.commander.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.d
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
if legion:IsRunning() and Runway then
-- Loops over cohorts.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
end
-- Target position.
local TargetVec2=Target:GetVec2()
-- Recruite assets.
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2)
return recruited, assets, legions
end
--- Recruit assets for a given OPS zone.
-- @param #CHIEF self
@ -2698,23 +2828,7 @@ end
function CHIEF:RecruitAssetsForZone(StratZone, Resource)
-- Cohorts.
local Cohorts={}
for _,_legion in pairs(self.commander.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
if legion:IsRunning() and Runway then
-- Loops over cohorts.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
end
local Cohorts=self.commander:_GetCohorts()
-- Shortcuts.
local MissionType=Resource.MissionType

View File

@ -48,6 +48,7 @@
-- @field #table tacanChannel List of TACAN channels available to the cohort.
-- @field #number weightAsset Weight of one assets group in kg.
-- @field #number cargobayLimit Cargo bay capacity in kg.
-- @field #table operations Operations this cohort is part of.
-- @extends Core.Fsm#FSM
--- *I came, I saw, I conquered.* -- Julius Caesar
@ -82,6 +83,7 @@ COHORT = {
cargobayLimit = 0,
descriptors = {},
properties = {},
operations = {},
}
--- COHORT class version.
@ -240,8 +242,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
-- @param #number delay Delay in seconds.
--- On after "Pause" event.
-- @function [parent=#AUFTRAG] OnAfterPause
-- @param #AUFTRAG self
-- @function [parent=#COHORT] OnAfterPause
-- @param #COHORT self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
@ -257,8 +259,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
-- @param #number delay Delay in seconds.
--- On after "Unpause" event.
-- @function [parent=#AUFTRAG] OnAfterUnpause
-- @param #AUFTRAG self
-- @function [parent=#COHORT] OnAfterUnpause
-- @param #COHORT self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
@ -274,8 +276,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
-- @param #number delay Delay in seconds.
--- On after "Relocate" event.
-- @function [parent=#AUFTRAG] OnAfterRelocate
-- @param #AUFTRAG self
-- @function [parent=#COHORT] OnAfterRelocate
-- @param #COHORT self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
@ -291,8 +293,8 @@ function COHORT:New(TemplateGroupName, Ngroups, CohortName)
-- @param #number delay Delay in seconds.
--- On after "Relocated" event.
-- @function [parent=#AUFTRAG] OnAfterRelocated
-- @param #AUFTRAG self
-- @function [parent=#COHORT] OnAfterRelocated
-- @param #COHORT self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
@ -785,7 +787,7 @@ end
function COHORT:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting %s v%s %s", self.ClassName, self.version, self.name)
local text=string.format("Starting %s v%s %s [%s]", self.ClassName, self.version, self.name, self.attribute)
self:I(self.lid..text)
-- Start the status monitoring.
@ -987,6 +989,30 @@ function COHORT:CountAssets(InStock, MissionTypes, Attributes)
return N
end
--- Get OPSGROUPs.
-- @param #COHORT self
-- @param #table MissionTypes (Optional) Count only assest that can perform certain mission type(s). Default is all types.
-- @param #table Attributes (Optional) Count only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`.
-- @return Core.Set#SET_OPSGROUP Ops groups set.
function COHORT:GetOpsGroups(MissionTypes, Attributes)
local set=SET_OPSGROUP:New()
for _,_asset in pairs(self.assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
if MissionTypes==nil or AUFTRAG.CheckMissionCapability(MissionTypes, self.missiontypes) then
if Attributes==nil or self:CheckAttribute(Attributes) then
if asset.flightgroup and asset.flightgroup:IsAlive() then
set:AddGroup(asset.flightgroup)
end
end
end
end
return set
end
--- Get assets for a mission.
-- @param #COHORT self
-- @param #string MissionType Mission type.
@ -1021,7 +1047,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads)
if not (isRequested or isReserved) then
-- Check if asset is currently on a mission (STARTED or QUEUED).
if self.legion:IsAssetOnMission(asset) then
if self.legion:IsAssetOnMission(asset) then
---
-- Asset is already on a mission.
---
@ -1034,7 +1060,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads)
elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.NOTHING) then
-- Relocation: Take all assets. Mission will be cancelled.
-- Assets on mission NOTHING are considered.
table.insert(assets, asset)
elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.GCICAP) and MissionType==AUFTRAG.Type.INTERCEPT then
@ -1058,7 +1084,7 @@ function COHORT:RecruitAssets(MissionType, Npayloads)
table.insert(assets, asset)
end
elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) then
elseif self.legion:IsAssetOnMission(asset, AUFTRAG.Type.ALERT5) and AUFTRAG.CheckMissionCapability(MissionType, asset.payload.capabilities) and MissionType~=AUFTRAG.Type.ALERT5 then
-- Check if the payload of this asset is compatible with the mission.
self:T(self.lid..string.format("Adding asset on ALERT 5 mission for %s mission", MissionType))
@ -1519,7 +1545,17 @@ function COHORT:_MissileCategoryName(categorynumber)
cat="other"
end
return cat
end
end
--- Add an OPERATION.
-- @param #COHORT self
-- @param Ops.Operation#OPERATION Operation The operation this cohort is part of.
-- @return #COHORT self
function COHORT:_AddOperation(Operation)
self.operations[Operation.name]=Operation
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -24,6 +24,8 @@
-- @field #table legions Table of legions which are commanded.
-- @field #table missionqueue Mission queue.
-- @field #table transportqueue Transport queue.
-- @field #table targetqueue Target queue.
-- @field #table opsqueue Operations queue.
-- @field #table rearmingZones Rearming zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field #table refuellingZones Refuelling zones. Each element is of type `#BRIGADE.SupplyZone`.
-- @field #table capZones CAP zones. Each element is of type `#AIRWING.PatrolZone`.
@ -125,6 +127,8 @@ COMMANDER = {
legions = {},
missionqueue = {},
transportqueue = {},
targetqueue = {},
opsqueue = {},
rearmingZones = {},
refuellingZones = {},
capZones = {},
@ -514,6 +518,69 @@ function COMMANDER:RemoveTransport(Transport)
return self
end
--- Add target.
-- @param #COMMANDER self
-- @param Ops.Target#TARGET Target Target object to be added.
-- @return #COMMANDER self
function COMMANDER:AddTarget(Target)
if not self:IsTarget(Target) then
table.insert(self.targetqueue, Target)
end
return self
end
--- Add operation.
-- @param #COMMANDER self
-- @param Ops.Operation#OPERATION Operation The operation to be added.
-- @return #COMMANDER self
function COMMANDER:AddOperation(Operation)
-- TODO: Check that is not already added.
-- Add operation to table.
table.insert(self.opsqueue, Operation)
return self
end
--- Check if a TARGET is already in the queue.
-- @param #COMMANDER self
-- @param Ops.Target#TARGET Target Target object to be added.
-- @return #boolean If `true`, target exists in the target queue.
function COMMANDER:IsTarget(Target)
for _,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
if target.uid==Target.uid or target:GetName()==Target:GetName() then
return true
end
end
return false
end
--- Remove target from queue.
-- @param #COMMANDER self
-- @param Ops.Target#TARGET Target The target.
-- @return #COMMANDER self
function COMMANDER:RemoveTarget(Target)
for i,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
if target.uid==Target.uid then
self:T(self.lid..string.format("Removing target %s from queue", Target.name))
table.remove(self.targetqueue, i)
break
end
end
return self
end
--- Add a rearming zone.
-- @param #COMMANDER self
-- @param Core.Zone#ZONE RearmingZone Rearming zone.
@ -786,9 +853,15 @@ function COMMANDER:onafterStatus(From, Event, To)
-- Status.
if self.verbose>=1 then
local text=string.format("Status %s: Legions=%d, Missions=%d, Transports", fsmstate, #self.legions, #self.missionqueue, #self.transportqueue)
local text=string.format("Status %s: Legions=%d, Missions=%d, Targets=%d, Transports=%d", fsmstate, #self.legions, #self.missionqueue, #self.targetqueue, #self.transportqueue)
self:T(self.lid..text)
end
-- Check Operations queue.
self:CheckOpsQueue()
-- Check target queue and add missions.
self:CheckTargetQueue()
-- Check mission queue and assign one PLANNED mission.
self:CheckMissionQueue()
@ -977,6 +1050,21 @@ function COMMANDER:onafterStatus(From, Event, To)
end
self:I(self.lid..text)
end
---
-- TARGETS
---
-- Target queue.
if self.verbose>=2 and #self.targetqueue>0 then
local text="Target queue:"
for i,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
text=text..string.format("\n[%d] %s: status=%s, life=%d", i, target:GetName(), target:GetState(), target:GetLife())
end
self:I(self.lid..text)
end
---
-- TRANSPORTS
@ -1148,6 +1236,160 @@ end
-- Mission Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check OPERATIONs queue.
-- @param #COMMANDER self
function COMMANDER:CheckOpsQueue()
-- Number of missions.
local Nops=#self.opsqueue
-- Treat special cases.
if Nops==0 then
return nil
end
-- Loop over operations.
for _,_ops in pairs(self.opsqueue) do
local operation=_ops --Ops.Operation#OPERATION
if operation:IsRunning() then
-- Loop over missions.
for _,_mission in pairs(operation.missions or {}) do
local mission=_mission --Ops.Auftrag#AUFTRAG
if mission.phase==nil or (mission.phase and mission.phase==operation.phase) and mission:IsPlanned() then
self:AddMission(mission)
end
end
-- Loop over targets.
for _,_target in pairs(operation.targets or {}) do
local target=_target --Ops.Target#TARGET
if (target.phase==nil or (target.phase and target.phase==operation.phase)) and (not self:IsTarget(target)) then
self:AddTarget(target)
end
end
end
end
end
--- Check target queue and assign ONE valid target by adding it to the mission queue of the COMMANDER.
-- @param #COMMANDER self
function COMMANDER:CheckTargetQueue()
-- Number of missions.
local Ntargets=#self.targetqueue
-- Treat special cases.
if Ntargets==0 then
return nil
end
-- Remove done targets.
for i=#self.targetqueue,1,-1 do
local target=self.targetqueue[i] --Ops.Target#TARGET
if (not target:IsAlive()) or target:EvalConditionsAny(target.conditionStop) then
for _,_resource in pairs(target.resources) do
local resource=_resource --Ops.Target#TARGET.Resource
if resource.mission and resource.mission:IsNotOver() then
self:MissionCancel(resource.mission)
end
end
table.remove(self.targetqueue, i)
end
end
-- Check if total number of missions is reached.
local NoLimit=self:_CheckMissionLimit("Total")
if NoLimit==false then
return nil
end
-- Sort results table wrt prio and threatlevel.
local function _sort(a, b)
local taskA=a --Ops.Target#TARGET
local taskB=b --Ops.Target#TARGET
return (taskA.prio<taskB.prio) or (taskA.prio==taskB.prio and taskA.threatlevel0>taskB.threatlevel0)
end
table.sort(self.targetqueue, _sort)
-- Get the lowest importance value (lower means more important).
-- If a target with importance 1 exists, targets with importance 2 will not be assigned. Targets with no importance (nil) can still be selected.
local vip=math.huge
for _,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
if target:IsAlive() and target.importance and target.importance<vip then
vip=target.importance
end
end
-- Loop over targets.
for _,_target in pairs(self.targetqueue) do
local target=_target --Ops.Target#TARGET
-- Is target still alive.
local isAlive=target:IsAlive()
-- Is this target important enough.
local isImportant=(target.importance==nil or target.importance<=vip)
-- Check ALL start conditions are true.
local isReadyStart=target:EvalConditionsAll(target.conditionStart)
-- Debug message.
local text=string.format("Target %s: Alive=%s, Important=%s", target:GetName(), tostring(isAlive), tostring(isImportant))
self:T2(self.lid..text)
-- Check that target is alive and not already a mission has been assigned.
if isAlive and isImportant then
for _,_resource in pairs(target.resources or {}) do
local resource=_resource --Ops.Target#TARGET.Resource
-- Mission type.
local missionType=resource.MissionType
if (not resource.mission) or resource.mission:IsOver() then
-- Debug info.
self:T2(self.lid..string.format("Target \"%s\" ==> Creating mission type %s: Nmin=%d, Nmax=%d", target:GetName(), missionType, resource.Nmin, resource.Nmax))
-- Create a mission.
local mission=AUFTRAG:NewFromTarget(target, missionType)
if mission then
-- Set mission parameters.
mission:SetRequiredAssets(resource.Nmin, resource.Nmax)
mission:SetRequiredAttribute(resource.Attributes)
mission:SetRequiredProperty(resource.Properties)
-- Set operation (if any).
mission.operation=target.operation
-- Set resource mission.
resource.mission=mission
-- Add mission to queue.
self:AddMission(resource.mission)
end
end
end
end
end
end
--- Check mission queue and assign ONE planned mission.
-- @param #COMMANDER self
function COMMANDER:CheckMissionQueue()
@ -1203,10 +1445,7 @@ function COMMANDER:CheckMissionQueue()
if recruited then
-- Add asset to mission.
for _,_asset in pairs(assets) do
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
mission:AddAsset(asset)
end
mission:_AddAssets(assets)
-- Recruit asset for escorting recruited mission assets.
local EscortAvail=self:RecruitAssetsForEscort(mission, assets)
@ -1262,6 +1501,121 @@ function COMMANDER:CheckMissionQueue()
end
--- Get cohorts.
-- @param #COMMANDER self
-- @param #table Legions Special legions.
-- @param #table Cohorts Special cohorts.
-- @param Ops.Operation#OPERATION Operation Operation.
-- @return #table Cohorts.
function COMMANDER:_GetCohorts(Legions, Cohorts, Operation)
--- Function that check if a legion or cohort is part of an operation.
local function CheckOperation(LegionOrCohort)
-- No operations ==> no problem!
if #self.opsqueue==0 then
return true
end
-- Cohort is not dedicated to a running(!) operation. We assume so.
local isAvail=true
-- Only available...
if Operation then
isAvail=false
end
for _,_operation in pairs(self.opsqueue) do
local operation=_operation --Ops.Operation#OPERATION
-- Legion is assigned to this operation.
local isOps=operation:IsAssignedCohortOrLegion(LegionOrCohort)
if isOps and operation:IsRunning() then
-- Is dedicated.
isAvail=false
if Operation==nil then
-- No Operation given and this is dedicated to at least one operation.
return false
else
if Operation.uid==operation.uid then
-- Operation given and is part of it.
return true
end
end
end
end
return isAvail
end
-- Chosen cohorts.
local cohorts={}
-- Check if there are any special legions and/or cohorts.
if (Legions and #Legions>0) or (Cohorts and #Cohorts>0) then
-- Add cohorts of special legions.
for _,_legion in pairs(Legions or {}) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
-- Legion has to be running.
if legion:IsRunning() and Runway then
-- Add cohorts of legion.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
if CheckOperation(cohort.legion) or CheckOperation(cohort) then
table.insert(cohorts, cohort)
end
end
end
end
-- Add special cohorts.
for _,_cohort in pairs(Cohorts or {}) do
local cohort=_cohort --Ops.Cohort#COHORT
if CheckOperation(cohort) then
table.insert(cohorts, cohort)
end
end
else
-- No special mission legions/cohorts found ==> take own legions.
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
-- Legion has to be running.
if legion:IsRunning() and Runway then
-- Add cohorts of legion.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
if CheckOperation(cohort.legion) or CheckOperation(cohort) then
table.insert(cohorts, cohort)
end
end
end
end
end
return cohorts
end
--- Recruit assets for a given mission.
-- @param #COMMANDER self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
@ -1274,29 +1628,10 @@ function COMMANDER:RecruitAssetsForMission(Mission)
self:T2(self.lid..string.format("Recruiting assets for mission \"%s\" [%s]", Mission:GetName(), Mission:GetType()))
-- Cohorts.
local Cohorts={}
for _,_legion in pairs(Mission.specialLegions or {}) do
local legion=_legion --Ops.Legion#LEGION
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
for _,_cohort in pairs(Mission.specialCohorts or {}) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
local Cohorts=self:_GetCohorts(Mission.specialLegions, Mission.specialCohorts, Mission.operation)
-- No special mission legions/cohorts found ==> take own legions.
if #Cohorts==0 then
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
end
-- Debug info.
self:T(self.lid..string.format("Found %d cohort candidates for mission", #Cohorts))
-- Number of required assets.
local NreqMin, NreqMax=Mission:GetRequiredAssets()
@ -1325,30 +1660,7 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets)
if Mission.NescortMin and Mission.NescortMax and (Mission.NescortMin>0 or Mission.NescortMax>0) then
-- Cohorts.
local Cohorts={}
for _,_legion in pairs(Mission.escortLegions or {}) do
local legion=_legion --Ops.Legion#LEGION
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
for _,_cohort in pairs(Mission.escortCohorts or {}) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
-- No special escort legions/cohorts found ==> take own legions.
if #Cohorts==0 then
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
end
local Cohorts=self:_GetCohorts(Mission.escortLegions, Mission.escortCohorts, Mission.operation)
-- Call LEGION function but provide COMMANDER as self.
local assigned=LEGION.AssignAssetsForEscort(self, Cohorts, Assets, Mission.NescortMin, Mission.NescortMax, Mission.escortTargetTypes, Mission.escortEngageRange)
@ -1359,6 +1671,30 @@ function COMMANDER:RecruitAssetsForEscort(Mission, Assets)
return true
end
--- Recruit assets for a given TARGET.
-- @param #COMMANDER self
-- @param Ops.Target#TARGET Target The target.
-- @param #string MissionType Mission Type.
-- @param #number NassetsMin Min number of required assets.
-- @param #number NassetsMax Max number of required assets.
-- @return #boolean If `true` enough assets could be recruited.
-- @return #table Assets that have been recruited from all legions.
-- @return #table Legions that have recruited assets.
function COMMANDER:RecruitAssetsForTarget(Target, MissionType, NassetsMin, NassetsMax)
-- Cohorts.
local Cohorts=self:_GetCohorts()
-- Target position.
local TargetVec2=Target:GetVec2()
-- Recruite assets.
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2)
return recruited, assets, legions
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Transport Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1478,24 +1814,7 @@ function COMMANDER:RecruitAssetsForTransport(Transport, CargoWeight, TotalWeight
end
-- Cohorts.
local Cohorts={}
for _,_legion in pairs(self.legions) do
local legion=_legion --Ops.Legion#LEGION
-- Check that runway is operational.
local Runway=legion:IsAirwing() and legion:IsRunwayOperational() or true
if legion:IsRunning() and Runway then
-- Loops over cohorts.
for _,_cohort in pairs(legion.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
table.insert(Cohorts, cohort)
end
end
end
local Cohorts=self:_GetCohorts()
-- Target is the deploy zone.
local TargetVec2=Transport:GetDeployZone():GetVec2()

View File

@ -108,6 +108,15 @@ function FLEET:New(WarehouseName, FleetName)
-- Defaults
self:SetRetreatZones()
-- Turn ship into NAVYGROUP.
if self:IsShip() then
local wh=self.warehouse --Wrapper.Unit#UNIT
local group=wh:GetGroup()
self.warehouseOpsGroup=NAVYGROUP:New(group) --Ops.NavyGroup#NAVYGROUP
self.warehouseOpsElement=self.warehouseOpsGroup:GetElementByName(wh:GetName())
end
-- Add FSM transitions.
-- From State --> Event --> To State

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1453,6 +1453,9 @@ function INTEL:PaintPicture()
self:AddContactToCluster(contact, cluster)
else
-- Debug info.
self:T(self.lid..string.format("Paint Picture: contact %s has no closest cluster ==> Create new cluster", contact.groupname))
-- Create a brand new cluster.
local newcluster=self:_CreateClusterFromContact(contact)
@ -1817,13 +1820,13 @@ function INTEL:IsContactConnectedToCluster(contact, cluster)
--local dist=Contact.position:Get2DDistance(contact.position)
local dist=Contact.position:DistanceFromPointVec2(contact.position)
-- AIR - check for spatial proximity
local airprox = false
-- AIR - check for spatial proximity (corrected because airprox was always false for ctype~=INTEL.Ctype.AIRCRAFT)
local airprox = true
if contact.ctype == INTEL.Ctype.AIRCRAFT then
self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,contact.altitude))
local adist = math.abs(cluster.altitude - contact.altitude)
if adist < UTILS.FeetToMeters(10000) then -- limit to 10kft
airprox = true
if adist > UTILS.FeetToMeters(10000) then -- limit to 10kft
airprox = false
end
end
@ -1903,17 +1906,17 @@ function INTEL:_GetClosestClusterOfContact(Contact)
local dist=self:_GetDistContactToCluster(Contact, cluster)
-- AIR - check for spatial proximity
local airprox = false
-- AIR - check for spatial proximity (ff: Changed because airprox was always false for ctype~=AIRCRAFT!)
local airprox=true
if Contact.ctype == INTEL.Ctype.AIRCRAFT then
if not cluster.altitude then
cluster.altitude = self:GetClusterAltitude(cluster,true)
end
local adist = math.abs(cluster.altitude - Contact.altitude)
self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,Contact.altitude))
if adist < UTILS.FeetToMeters(10000) then
airprox = true
end
if not cluster.altitude then
cluster.altitude = self:GetClusterAltitude(cluster,true)
end
local adist = math.abs(cluster.altitude - Contact.altitude)
self:T(string.format("Cluster Alt=%d | Contact Alt=%d",cluster.altitude,Contact.altitude))
if adist > UTILS.FeetToMeters(10000) then
airprox = false
end
end
if dist<distmin and airprox then

View File

@ -1191,6 +1191,12 @@ function LEGION:onafterOpsOnMission(From, Event, To, OpsGroup, Mission)
self:NavyOnMission(OpsGroup, Mission)
end
-- Load group as cargo because it cannot swim! We pause the mission.
if self:IsBrigade() and self:IsShip() then
OpsGroup:PauseMission()
self.warehouseOpsGroup:Load(OpsGroup, self.warehouseOpsElement)
end
-- Trigger event for chief.
if self.chief then
self.chief:OpsOnMission(OpsGroup, Mission)
@ -1609,7 +1615,7 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #WAREHOUSE.Pendingitem Request Information table of the request.
-- @param Functional.Warehouse#WAREHOUSE.Pendingitem Request Information table of the request.
-- @param Core.Set#SET_GROUP CargoGroupSet Set of cargo groups.
-- @param Core.Set#SET_GROUP TransportGroupSet Set of transport groups if any.
function LEGION:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet, TransportGroupSet)
@ -1858,6 +1864,31 @@ function LEGION:CountAssets(InStock, MissionTypes, Attributes)
return N
end
--- Get OPSGROUPs that are spawned and alive.
-- @param #LEGION self
-- @param #table MissionTypes (Optional) Get only assest that can perform certain mission type(s). Default is all types.
-- @param #table Attributes (Optional) Get only assest that have a certain attribute(s), e.g. `WAREHOUSE.Attribute.AIR_BOMBER`.
-- @return Core.Set#SET_OPSGROUP The set of OPSGROUPs. Can be empty if no groups are spawned or alive!
function LEGION:GetOpsGroups(MissionTypes, Attributes)
local setLegion=SET_OPSGROUP:New()
for _,_cohort in pairs(self.cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
-- Get cohort set.
local setCohort=cohort:GetOpsGroups(MissionTypes, Attributes)
-- Debug info.
self:T2(self.lid..string.format("Found %d opsgroups of cohort %s", setCohort:Count(), cohort.name))
-- Add to legion set.
setLegion:AddSet(setCohort)
end
return setLegion
end
--- Count total number of assets in LEGION warehouse stock that also have a payload.
-- @param #LEGION self
-- @param #boolean Payloads (Optional) Specifc payloads to consider. Default all.
@ -2246,7 +2277,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
local cohort=_cohort --Ops.Cohort#COHORT
if Properties and #Properties>0 then
for _,Property in pairs(Properties) do
for _,property in pairs(cohort.properties) do
for property,value in pairs(cohort.properties) do
if Property==property then
return true
end
@ -2277,8 +2308,8 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
else
return true
end
end
end
-- Loops over cohorts.
for _,_cohort in pairs(Cohorts) do
local cohort=_cohort --Ops.Cohort#COHORT
@ -2329,7 +2360,7 @@ function LEGION.RecruitCohortAssets(Cohorts, MissionTypeRecruit, MissionTypeOpt,
end
-- Debug info.
cohort:T2(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, Category=%s, Attribute=%s, Property=%s, Weapon=%s",
cohort:T(cohort.lid..string.format("State=%s: Capable=%s, InRange=%s, Refuel=%s, CanCarry=%s, Category=%s, Attribute=%s, Property=%s, Weapon=%s",
cohort:GetState(), tostring(Capable), tostring(InRange), tostring(Refuel), tostring(CanCarry), tostring(RightCategory), tostring(RightAttribute), tostring(RightProperty), tostring(RightWeapon)))
-- Check OnDuty, capable, in range and refueling type (if TANKER).
@ -2664,15 +2695,15 @@ function LEGION:AssignAssetsForTransport(Legions, CargoAssets, NcarriersMin, Nca
-- Set pickup zone to spawn zone or airbase if the legion has one that is operational.
local pickupzone=legion.spawnzone
if legion.airbase and legion:IsRunwayOperational() then
--pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000)
end
-- Add TZC from legion spawn zone to deploy zone.
local tpz=Transport:AddTransportZoneCombo(nil, pickupzone, Transport:GetDeployZone())
tpz.PickupAirbase=legion:IsRunwayOperational() and legion.airbase or nil
Transport:SetEmbarkZone(legion.spawnzone, tpz)
-- Set pickup airbase if the legion has an airbase. Could also be the ship itself.
tpz.PickupAirbase=legion:IsRunwayOperational() and legion.airbase or nil
-- Set embark zone to spawn zone.
Transport:SetEmbarkZone(legion.spawnzone, tpz)
-- Add cargo assets to transport.
for _,_asset in pairs(CargoAssets) do
@ -2775,8 +2806,8 @@ function LEGION.CalculateAssetMissionScore(asset, MissionType, TargetVec2, Inclu
-- Prefer assets that are on ALERT5 for this mission type.
score=score+25
elseif currmission.type==AUFTRAG.Type.GCICAP and MissionType==AUFTRAG.Type.INTERCEPT then
-- Prefer assets that are on GCICAP to perform INTERCEPTS
score=score+25
-- Prefer assets that are on GCICAP to perform INTERCEPTS. We set this even higher than alert5 because they are already in the air.
score=score+35
elseif (currmission.type==AUFTRAG.Type.ONGUARD or currmission.type==AUFTRAG.Type.PATROLZONE) and (MissionType==AUFTRAG.Type.ARTY or MissionType==AUFTRAG.Type.GROUNDATTACK) then
score=score+25
elseif currmission.type==AUFTRAG.Type.NOTHING then

View File

@ -90,7 +90,7 @@ NAVYGROUP = {
--- NavyGroup version.
-- @field #string version
NAVYGROUP.version="0.7.3"
NAVYGROUP.version="0.7.9"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
@ -780,7 +780,11 @@ function NAVYGROUP:Status(From, Event, To)
if timer.getAbsTime()>self.Twaiting+self.dTwait then
self.Twaiting=nil
self.dTwait=nil
self:Cruise()
if self:_CountPausedMissions()>0 then
self:UnpauseMission()
else
self:Cruise()
end
end
end
end
@ -987,8 +991,17 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
-- Set default Alarm State.
self:SwitchAlarmstate(self.option.Alarm)
-- Set emission.
self:SwitchEmission(self.option.Emission)
-- Set default EPLRS.
self:SwitchEPLRS(self.option.EPLRS)
self:SwitchEPLRS(self.option.EPLRS)
-- Set default Invisible.
self:SwitchInvisible(self.option.Invisible)
-- Set default Immortal.
self:SwitchImmortal(self.option.Immortal)
-- Set TACAN beacon.
self:_SwitchTACAN()

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -2171,7 +2171,7 @@ function OPSTRANSPORT:_GetTransportZoneCombo(Carrier)
return nil
end
--- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUUP, an OPSGROUP is created automatically.
--- Get an OPSGROUP from a given OPSGROUP or GROUP object. If the object is a GROUP, an OPSGROUP is created automatically.
-- @param #OPSTRANSPORT self
-- @param Core.Base#BASE Object The object, which can be a GROUP or OPSGROUP.
-- @return Ops.OpsGroup#OPSGROUP Ops Group.

View File

@ -2,8 +2,9 @@
--
-- **Main Features:**
--
-- * Monitor if a zone is captured.
-- * Monitor if an airbase is captured.
-- * Monitor if a zone is captured
-- * Monitor if an airbase is captured
-- * Define conditions under which zones are captured/held
--
-- ===
--
@ -79,6 +80,7 @@ OPSZONE.version="0.3.0"
-- ToDo list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Capturing based on (total) threat level threshold. Unarmed units do not pose a threat and should not be able to hold a zone.
-- TODO: Pause/unpause evaluations.
-- TODO: Capture time, i.e. time how long a single coalition has to be inside the zone to capture it.
-- TODO: Differentiate between ground attack and boming by air or arty.
@ -348,7 +350,7 @@ end
--- Set categories of units that can capture or hold the zone. See [DCS Class Unit](https://wiki.hoggitworld.com/view/DCS_Class_Unit).
-- @param #OPSZONE self
-- @param #table Categories Table of unit categories. Default `{Unit.Category.GROUND_UNIT}`.
-- @return #OPSZONE
-- @return #OPSZONE self
function OPSZONE:SetUnitCategories(Categories)
-- Ensure table.
@ -362,6 +364,32 @@ function OPSZONE:SetUnitCategories(Categories)
return self
end
--- Set threat level threshold that the defending units must have to hold a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to hold a zone as they do not pose a threat.
-- @param #OPSZONE self
-- @param #number Threatlevel Threat level threshod. Default 0.
-- @return #OPSZONE self
function OPSZONE:SetThreatlevelDefinding(Threatlevel)
self.threatlevelDefending=Threatlevel or 0
return self
end
--- Set threat level threshold that the offending units must have to capture a zone.
-- The reason why you might want to set this is that unarmed units (*e.g.* fuel trucks) should not be able to capture a zone as they do not pose a threat.
-- @param #OPSZONE self
-- @param #number Threatlevel Threat level threshod. Default 0.
-- @return #OPSZONE self
function OPSZONE:SetThreatlevelOffending(Threatlevel)
self.threatlevelOffending=Threatlevel or 0
return self
end
--- Set whether *neutral* units can capture the zone.
-- @param #OPSZONE self
-- @param #boolean CanCapture If `true`, neutral units can.

View File

@ -100,6 +100,7 @@ end
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--[[
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #PLATOON self
-- @param #string From From state.
@ -114,6 +115,7 @@ function PLATOON:onafterStart(From, Event, To)
-- Start the status monitoring.
self:__Status(-1)
end
]]
--- On after "Status" event.
-- @param #PLATOON self

View File

@ -36,6 +36,9 @@
-- @field Ops.Auftrag#AUFTRAG mission Mission attached to this target.
-- @field Ops.Intelligence#INTEL.Contact contact Contact attached to this target.
-- @field #boolean isDestroyed If true, target objects were destroyed.
-- @field #table resources Resource list.
-- @field #table conditionStart Start condition functions.
-- @field Ops.Operation#OPERATION operation Operation this target is part of.
-- @extends Core.Fsm#FSM
--- **It is far more important to be able to hit the target than it is to haggle over who makes a weapon or who pulls a trigger** -- Dwight D Eisenhower
@ -64,7 +67,8 @@ TARGET = {
Ndead = 0,
elements = {},
casualties = {},
threatlevel0 = 0
threatlevel0 = 0,
conditionStart = {},
}
@ -113,6 +117,16 @@ TARGET.ObjectStatus={
ALIVE="Alive",
DEAD="Dead",
}
--- Resource.
-- @type TARGET.Resource
-- @field #string MissionType Mission type, e.g. `AUFTRAG.Type.BAI`.
-- @field #number Nmin Min number of assets.
-- @field #number Nmax Max number of assets.
-- @field #table Attributes Generalized attribute, e.g. `{GROUP.Attribute.GROUND_INFANTRY}`.
-- @field #table Properties Properties ([DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes)), e.g. `"Attack helicopters"` or `"Mobile AAA"`.
-- @field Ops.Auftrag#AUFTRAG mission Attached mission.
--- Target object.
-- @type TARGET.Object
-- @field #number ID Target unique ID.
@ -307,6 +321,142 @@ function TARGET:SetImportance(Importance)
return self
end
--- Add start condition.
-- @param #TARGET self
-- @param #function ConditionFunction Function that needs to be true before the mission can be started. Must return a #boolean.
-- @param ... Condition function arguments if any.
-- @return #TARGET self
function TARGET:AddConditionStart(ConditionFunction, ...)
local condition={} --Ops.Auftrag#AUFTRAG.Condition
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionStart, condition)
return self
end
--- Add stop condition.
-- @param #TARGET self
-- @param #function ConditionFunction Function that needs to be true before the mission can be started. Must return a #boolean.
-- @param ... Condition function arguments if any.
-- @return #TARGET self
function TARGET:AddConditionStop(ConditionFunction, ...)
local condition={} --Ops.Auftrag#AUFTRAG.Condition
condition.func=ConditionFunction
condition.arg={}
if arg then
condition.arg=arg
end
table.insert(self.conditionStop, condition)
return self
end
--- Check if all given condition are true.
-- @param #TARGET self
-- @param #table Conditions Table of conditions.
-- @return #boolean If true, all conditions were true. Returns false if at least one condition returned false.
function TARGET:EvalConditionsAll(Conditions)
-- Any stop condition must be true.
for _,_condition in pairs(Conditions or {}) do
local condition=_condition --Ops.Auftrag#AUFTRAG.Condition
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any false will return false.
if not istrue then
return false
end
end
-- All conditions were true.
return true
end
--- Check if any of the given conditions is true.
-- @param #TARGET self
-- @param #table Conditions Table of conditions.
-- @return #boolean If true, at least one condition is true.
function TARGET:EvalConditionsAny(Conditions)
-- Any stop condition must be true.
for _,_condition in pairs(Conditions or {}) do
local condition=_condition --Ops.Auftrag#AUFTRAG.Condition
-- Call function.
local istrue=condition.func(unpack(condition.arg))
-- Any true will return true.
if istrue then
return true
end
end
-- No condition was true.
return false
end
--- Add mission type and number of required assets to resource.
-- @param #TARGET self
-- @param #string MissionType Mission Type.
-- @param #number Nmin Min number of required assets.
-- @param #number Nmax Max number of requried assets.
-- @param #table Attributes Generalized attribute(s).
-- @param #table Properties DCS attribute(s). Default `nil`.
-- @return #TARGET.Resource The resource table.
function TARGET:AddResource(MissionType, Nmin, Nmax, Attributes, Properties)
-- Ensure table.
if Attributes and type(Attributes)~="table" then
Attributes={Attributes}
end
-- Ensure table.
if Properties and type(Properties)~="table" then
Properties={Properties}
end
-- Create new resource table.
local resource={} --#TARGET.Resource
resource.MissionType=MissionType
resource.Nmin=Nmin or 1
resource.Nmax=Nmax or 1
resource.Attributes=Attributes or {}
resource.Properties=Properties or {}
-- Init resource table.
self.resources=self.resources or {}
-- Add to table.
table.insert(self.resources, resource)
-- Debug output.
if self.verbose>10 then
local text="Resource:"
for _,_r in pairs(self.resources) do
local r=_r --#TARGET.Resource
text=text..string.format("\nmission=%s, Nmin=%d, Nmax=%d, attribute=%s, properties=%s", r.MissionType, r.Nmin, r.Nmax, tostring(r.Attributes[1]), tostring(r.Properties[1]))
end
self:I(self.lid..text)
end
return resource
end
--- Check if TARGET is alive.
-- @param #TARGET self
-- @return #boolean If true, target is alive.

View File

@ -529,37 +529,41 @@ function MSRS:PlayText(Text, Delay)
-- Execute command.
self:_ExecCommand(command)
--[[
-- Check that length of command is max 255 chars or os.execute() will not work!
if string.len(command)>255 then
-- Create a tmp file.
local filename = os.getenv('TMP') .. "\\MSRS-"..STTS.uuid()..".bat"
local script = io.open(filename, "w+")
script:write(command.." && exit")
script:close()
-- Play command.
command=string.format("\"%s\"", filename)
-- Play file in 0.05 seconds
timer.scheduleFunction(os.execute, command, timer.getTime()+0.05)
-- Remove file in 1 second.
timer.scheduleFunction(os.remove, filename, timer.getTime()+1)
else
-- Debug output.
self:I(string.format("MSRS Text command=%s", command))
end
-- Execute SRS command.
local x=os.execute(command)
return self
end
--- Play text message via STTS with explicitly specified options.
-- @param #MSRS self
-- @param #string Text Text message.
-- @param #number Delay Delay in seconds, before the message is played.
-- @return #MSRS self
function MSRS:PlayTextExt(Text, Delay, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MSRS.PlayTextExt, self, Text, 0, Frequencies, Modulations, Gender, Culture, Voice, Volume, Label)
else
-- Ensure table.
if Frequencies and type(Frequencies)~="table" then
Frequencies={Frequencies}
end
-- Ensure table.
if Modulations and type(Modulations)~="table" then
Modulations={Modulations}
end
-- Get command line.
local command=self:_GetCommand(Frequencies, Modulations, nil, Gender, Voice, Culture, Volume, nil, nil, Label)
-- Append text.
command=command..string.format(" --text=\"%s\"", tostring(Text))
end
-- Execute command.
self:_ExecCommand(command)
]]
end
return self

View File

@ -1155,7 +1155,7 @@ function UTILS.VecHdg(a)
end
--- Calculate "heading" of a 2D vector in the X-Y plane.
-- @param DCS#Vec2 a Vector in "D with x, y components.
-- @param DCS#Vec2 a Vector in 2D with x, y components.
-- @return #number Heading in degrees in [0,360).
function UTILS.Vec2Hdg(a)
local h=math.deg(math.atan2(a.y, a.x))

View File

@ -25,9 +25,11 @@
-- @field #boolean isShip Airbase is a ship.
-- @field #table parking Parking spot data.
-- @field #table parkingByID Parking spot data table with ID as key.
-- @field #number activerwyno Active runway number (forced).
-- @field #table parkingWhitelist List of parking spot terminal IDs considered for spawning.
-- @field #table parkingBlacklist List of parking spot terminal IDs **not** considered for spawning.
-- @field #table runways Runways of airdromes.
-- @field #AIRBASE.Runway runwayLanding Runway used for landing.
-- @field #AIRBASE.Runway runwayTakeoff Runway used for takeoff.
-- @extends Wrapper.Positionable#POSITIONABLE
--- Wrapper class to handle the DCS Airbase objects:
@ -69,7 +71,6 @@ AIRBASE = {
[Airbase.Category.HELIPAD] = "Helipad",
[Airbase.Category.SHIP] = "Ship",
},
activerwyno=nil,
}
--- Enumeration to identify the airbases in the Caucasus region.
@ -522,8 +523,9 @@ AIRBASE.SouthAtlantic={
-- @field #string AirbaseName Name of the airbase.
-- @field #number MarkerID Numerical ID of marker placed at parking spot.
-- @field Wrapper.Marker#MARKER Marker The marker on the F10 map.
-- @field #string ClientSpot Client unit sitting at this spot or *nil*.
-- @field #string Status Status of spot e.g. AIRBASE.SpotStatus.FREE.
-- @field #string ClientSpot If `true`, this is a parking spot of a client aircraft.
-- @field #string ClientName Client unit name of this spot.
-- @field #string Status Status of spot e.g. `AIRBASE.SpotStatus.FREE`.
-- @field #string OccupiedBy Name of the aircraft occupying the spot or "unknown". Can be *nil* if spot is not occupied.
-- @field #string ReservedBy Name of the aircraft for which this spot is reserved. Can be *nil* if spot is not reserved.
@ -573,11 +575,17 @@ AIRBASE.SpotStatus = {
--- Runway data.
-- @type AIRBASE.Runway
-- @field #number heading Heading of the runway in degrees.
-- @field #string name Runway name.
-- @field #string idx Runway ID: heading 070° ==> idx="07".
-- @field #number heading True heading of the runway in degrees.
-- @field #number magheading Magnetic heading of the runway in degrees. This is what is marked on the runway.
-- @field #number length Length of runway in meters.
-- @field #number width Width of runway in meters.
-- @field Core.Zone#ZONE_POLYGON zone Runway zone.
-- @field Core.Point#COORDINATE center Center of the runway.
-- @field Core.Point#COORDINATE position Position of runway start.
-- @field Core.Point#COORDINATE endpoint End point of runway.
-- @field #boolean isLeft If `true`, this is the left of two parallel runways. If `false`, this is the right of two runways. If `nil`, no parallel runway exists.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Registration
@ -624,6 +632,14 @@ function AIRBASE:Register(AirbaseName)
else
self:E("ERROR: Unknown airbase category!")
end
-- Init Runways.
self:_InitRunways()
-- Set the active runways based on wind direction.
if self.isAirdrome then
self:SetActiveRunway()
end
-- Init parking spots.
self:_InitParkingSpots()
@ -841,6 +857,42 @@ function AIRBASE:SetParkingSpotBlacklist(TerminalIdBlacklist)
return self
end
--- Sets the ATC belonging to an airbase object to be silent and unresponsive. This is useful for disabling the award winning ATC behavior in DCS.
-- Note that this DOES NOT remove the airbase from the list. It just makes it unresponsive and silent to any radio calls to it.
-- @param #AIRBASE self
-- @param #boolean Silent If `true`, enable silent mode. If `false` or `nil`, disable silent mode.
-- @return #AIRBASE self
function AIRBASE:SetRadioSilentMode(Silent)
-- Get DCS airbase object.
local airbase=self:GetDCSObject()
-- Set mode.
if airbase then
airbase:setRadioSilentMode(Silent)
end
return self
end
--- Check whether or not the airbase has been silenced.
-- @param #AIRBASE self
-- @return #boolean If `true`, silent mode is enabled.
function AIRBASE:GetRadioSilentMode()
-- Is silent?
local silent=nil
-- Get DCS airbase object.
local airbase=self:GetDCSObject()
-- Set mode.
if airbase then
silent=airbase:getRadioSilentMode()
end
return silent
end
--- Get category of airbase.
-- @param #AIRBASE self
@ -1022,6 +1074,23 @@ function AIRBASE:_InitParkingSpots()
self.NparkingTerminal[terminalType]=0
end
-- Get client coordinates.
local function isClient(coord)
local clients=_DATABASE.CLIENTS
for clientname, client in pairs(clients) do
local template=_DATABASE:GetGroupTemplateFromUnitName(clientname)
local units=template.units
for i,unit in pairs(units) do
local Coord=COORDINATE:New(unit.x, unit.alt, unit.y)
local dist=Coord:Get2DDistance(coord)
if dist<2 then
return true, clientname
end
end
end
return false, nil
end
-- Put coordinates of parking spots into table.
for _,spot in pairs(parkingdata) do
@ -1035,6 +1104,8 @@ function AIRBASE:_InitParkingSpots()
park.TerminalID0=spot.Term_Index_0
park.TerminalType=spot.Term_Type
park.TOAC=spot.TO_AC
park.ClientSpot, park.ClientName=isClient(park.Coordinate)
park.AirbaseName=self.AirbaseName
self.NparkingTotal=self.NparkingTotal+1
@ -1094,7 +1165,6 @@ function AIRBASE:GetParkingSpotsTable(termtype)
spot.Free=_isfree(_spot) -- updated
spot.TOAC=_spot.TO_AC -- updated
spot.AirbaseName=self.AirbaseName
spot.ClientSpot=nil --TODO
table.insert(spots, spot)
@ -1132,7 +1202,6 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC)
spot.Free=true -- updated
spot.TOAC=_spot.TO_AC -- updated
spot.AirbaseName=self.AirbaseName
spot.ClientSpot=nil --TODO
table.insert(freespots, spot)
@ -1484,6 +1553,248 @@ end
-- Runway
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get runways.
-- @param #AIRBASE self
-- @return #table Runway data.
function AIRBASE:GetRunways()
return self.runways or {}
end
--- Get runway by its name.
-- @param #AIRBASE self
-- @param #string Name Name of the runway, e.g. "31" or "21L".
-- @return #AIRBASE.Runway Runway data.
function AIRBASE:GetRunwayByName(Name)
if Name==nil then
return
end
if Name then
for _,_runway in pairs(self.runways) do
local runway=_runway --#AIRBASE.Runway
-- Name including L or R, e.g. "31L".
local name=self:GetRunwayName(runway)
if name==Name:upper() then
return runway
end
end
end
self:E("ERROR: Could not find runway with name "..tostring(Name))
return nil
end
--- Init runways.
-- @param #AIRBASE self
-- @param #boolean IncludeInverse If `true` or `nil`, include inverse runways.
-- @return #table Runway data.
function AIRBASE:_InitRunways(IncludeInverse)
-- Default is true.
if IncludeInverse==nil then
IncludeInverse=true
end
-- Runway table.
local Runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
self.runways={}
return {}
end
--- Function to create a runway data table.
local function _createRunway(name, course, width, length, center)
-- Bearing in rad.
local bearing=-1*course
-- Heading in degrees.
local heading=math.deg(bearing)
-- Data table.
local runway={} --#AIRBASE.Runway
runway.name=string.format("%02d", tonumber(name))
runway.magheading=tonumber(runway.name)*10
runway.heading=heading
runway.width=width or 0
runway.length=length or 0
runway.center=COORDINATE:NewFromVec3(center)
-- Ensure heading is [0,360]
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
-- For example at Nellis, DCS reports two runways, i.e. 03 and 21, BUT the "course" of both is -0.700 rad = 40 deg!
-- As a workaround, I check the difference between the "magnetic" heading derived from the name and the true heading.
-- If this is too large then very likely the "inverse" heading is the one we are looking for.
if math.abs(runway.heading-runway.magheading)>60 then
self:T(string.format("WARNING: Runway %s: heading=%.1f magheading=%.1f", runway.name, runway.heading, runway.magheading))
runway.heading=runway.heading-180
end
-- Ensure heading is [0,360]
if runway.heading>360 then
runway.heading=runway.heading-360
elseif runway.heading<0 then
runway.heading=runway.heading+360
end
-- Start and endpoint of runway.
runway.position=runway.center:Translate(-runway.length/2, runway.heading)
runway.endpoint=runway.center:Translate( runway.length/2, runway.heading)
local init=runway.center:GetVec3()
local width = runway.width/2
local L2=runway.length/2
local offset1 = {x = init.x + (math.cos(bearing + math.pi) * L2), y = init.z + (math.sin(bearing + math.pi) * L2)}
local offset2 = {x = init.x - (math.cos(bearing + math.pi) * L2), y = init.z - (math.sin(bearing + math.pi) * L2)}
local points={}
points[1] = {x = offset1.x + (math.cos(bearing + (math.pi/2)) * width), y = offset1.y + (math.sin(bearing + (math.pi/2)) * width)}
points[2] = {x = offset1.x + (math.cos(bearing - (math.pi/2)) * width), y = offset1.y + (math.sin(bearing - (math.pi/2)) * width)}
points[3] = {x = offset2.x + (math.cos(bearing - (math.pi/2)) * width), y = offset2.y + (math.sin(bearing - (math.pi/2)) * width)}
points[4] = {x = offset2.x + (math.cos(bearing + (math.pi/2)) * width), y = offset2.y + (math.sin(bearing + (math.pi/2)) * width)}
-- Runway zone.
runway.zone=ZONE_POLYGON_BASE:New(string.format("%s Runway %s", self.AirbaseName, runway.name), points)
return runway
end
-- Get DCS object.
local airbase=self:GetDCSObject()
if airbase then
-- Get DCS runways.
local runways=airbase:getRunways()
-- Debug info.
self:T2(runways)
if runways then
-- Loop over runways.
for _,rwy in pairs(runways) do
-- Debug info.
self:T(rwy)
-- Get runway data.
local runway=_createRunway(rwy.Name, rwy.course, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway
-- Add to table.
table.insert(Runways, runway)
-- Include "inverse" runway.
if IncludeInverse then
-- Create "inverse".
local idx=tonumber(runway.name)
local name2=tostring(idx-18)
if idx<18 then
name2=tostring(idx+18)
end
-- Create "inverse" runway.
local runway=_createRunway(name2, rwy.course-math.pi, rwy.width, rwy.length, rwy.position) --#AIRBASE.Runway
-- Add inverse to table.
table.insert(Runways, runway)
end
end
end
end
-- Look for identical (parallel) runways, e.g. 03L and 03R at Nellis.
local rpairs={}
for i,_ri in pairs(Runways) do
local ri=_ri --#AIRBASE.Runway
for j,_rj in pairs(Runways) do
local rj=_rj --#AIRBASE.Runway
if i<j then
if ri.name==rj.name then
rpairs[i]=j
end
end
end
end
local function isLeft(a, b, c)
--return ((b.x - a.x)*(c.z - a.z) - (b.z - a.z)*(c.x - a.x)) > 0
return ((b.z - a.z)*(c.x - a.x) - (b.x - a.x)*(c.z - a.z)) > 0
end
--[[
local a={x=1, y=0, z=0}
local A={x=0, y=0, z=0}
local b={x=0, y=0, z=1}
local c={x=0, y=0, z=-1}
local bl=isLeft(A, a, b)
local cl=isLeft(A, a, c)
env.info(string.format("b left=%s, c left=%s", tostring(bl), tostring(cl)))
]]
for i,j in pairs(rpairs) do
local ri=Runways[i] --#AIRBASE.Runway
local rj=Runways[j] --#AIRBASE.Runway
-- Draw arrow.
--ri.center:ArrowToAll(rj.center)
local c0=ri.center
-- Vector in the direction of the runway.
local a=UTILS.VecTranslate(c0, 1000, ri.heading)
-- Vector from runway i to runway j.
local b=UTILS.VecSubstract(rj.center, ri.center)
b=UTILS.VecAdd(ri.center, b)
--[[
local ca=COORDINATE:NewFromVec3(a)
local cb=COORDINATE:NewFromVec3(b)
c0:ArrowToAll(ca, nil , {0,1,0})
c0:ArrowToAll(cb, nil , {0,0,1})
]]
-- Check if rj is left of ri.
local left=isLeft(c0, a, b)
--env.info(string.format("Found pair %s: i=%d, j=%d, left==%s", ri.name, i, j, tostring(left)))
if left then
ri.isLeft=false
rj.isLeft=true
else
ri.isLeft=true
rj.isLeft=false
end
--break
end
-- Set runways.
self.runways=Runways
return Runways
end
--- Get runways data. Only for airdromes!
-- @param #AIRBASE self
-- @param #number magvar (Optional) Magnetic variation in degrees.
@ -1651,26 +1962,100 @@ function AIRBASE:GetRunwayData(magvar, mark)
return runways
end
--- Set the active runway in case it cannot be determined by the wind direction.
--- Set the active runway for landing and takeoff.
-- @param #AIRBASE self
-- @param #number iactive Number of the active runway in the runway data table.
function AIRBASE:SetActiveRunway(iactive)
self.activerwyno=iactive
-- @param #string Name Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction.
-- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right.
function AIRBASE:SetActiveRunway(Name, PreferLeft)
self:SetActiveRunwayTakeoff(Name, PreferLeft)
self:SetActiveRunwayLanding(Name,PreferLeft)
end
--- Get the active runway based on current wind direction.
--- Set the active runway for landing.
-- @param #AIRBASE self
-- @param #number magvar (Optional) Magnetic variation in degrees.
-- @return #AIRBASE.Runway Active runway data table.
function AIRBASE:GetActiveRunway(magvar)
-- @param #string Name Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction.
-- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right.
-- @return #AIRBASE.Runway The active runway for landing.
function AIRBASE:SetActiveRunwayLanding(Name, PreferLeft)
-- Get runways data (initialize if necessary).
local runways=self:GetRunwayData(magvar)
-- Return user forced active runway if it was set.
if self.activerwyno then
return runways[self.activerwyno]
local runway=self:GetRunwayByName(Name)
if not runway then
runway=self:GetRunwayIntoWind(PreferLeft)
end
if runway then
self:I("Setting active runway for landing as "..self:GetRunwayName(runway))
else
self:E("ERROR: Could not set the runway for landing!")
end
self.runwayLanding=runway
return runway
end
--- Get the active runways.
-- @param #AIRBASE self
-- @return #AIRBASE.Runway The active runway for landing.
-- @return #AIRBASE.Runway The active runway for takeoff.
function AIRBASE:GetActiveRunway()
return self.runwayLanding, self.runwayTakeoff
end
--- Get the active runway for landing.
-- @param #AIRBASE self
-- @return #AIRBASE.Runway The active runway for landing.
function AIRBASE:GetActiveRunwayLanding()
return self.runwayLanding
end
--- Get the active runway for takeoff.
-- @param #AIRBASE self
-- @return #AIRBASE.Runway The active runway for takeoff.
function AIRBASE:GetActiveRunwayTakeoff()
return self.runwayTakeoff
end
--- Set the active runway for takeoff.
-- @param #AIRBASE self
-- @param #string Name Name of the runway, e.g. "31" or "02L" or "90R". If not given, the runway is determined from the wind direction.
-- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right.
-- @return #AIRBASE.Runway The active runway for landing.
function AIRBASE:SetActiveRunwayTakeoff(Name, PreferLeft)
local runway=self:GetRunwayByName(Name)
if not runway then
runway=self:GetRunwayIntoWind(PreferLeft)
end
if runway then
self:I("Setting active runway for takeoff as "..self:GetRunwayName(runway))
else
self:E("ERROR: Could not set the runway for takeoff!")
end
self.runwayTakeoff=runway
return runway
end
--- Get the runway where aircraft would be taking of or landing into the direction of the wind.
-- NOTE that this requires the wind to be non-zero as set in the mission editor.
-- @param #AIRBASE self
-- @param #boolean PreferLeft If `true`, perfer the left runway. If `false`, prefer the right runway. If `nil` (default), do not care about left or right.
-- @return #AIRBASE.Runway Active runway data table.
function AIRBASE:GetRunwayIntoWind(PreferLeft)
-- Get runway data.
local runways=self:GetRunways()
-- Get wind vector.
local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3()
@ -1691,33 +2076,64 @@ function AIRBASE:GetActiveRunway(magvar)
local dotmin=nil
for i,_runway in pairs(runways) do
local runway=_runway --#AIRBASE.Runway
if PreferLeft==nil or PreferLeft==runway.isLeft then
-- Angle in rad.
local alpha=math.rad(runway.heading)
-- Runway vector.
local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)}
-- Dot product: parallel component of the two vectors.
local dot=UTILS.VecDot(Vwind, Vrunway)
-- Debug.
--env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot))
-- New min?
if dotmin==nil or dot<dotmin then
dotmin=dot
iact=i
-- Angle in rad.
local alpha=math.rad(runway.heading)
-- Runway vector.
local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)}
-- Dot product: parallel component of the two vectors.
local dot=UTILS.VecDot(Vwind, Vrunway)
-- New min?
if dotmin==nil or dot<dotmin then
dotmin=dot
iact=i
end
end
end
else
self:E("WARNING: Norm of wind is zero! Cannot determine active runway based on wind direction.")
self:E("WARNING: Norm of wind is zero! Cannot determine runway based on wind direction")
end
return runways[iact]
end
--- Get name of a given runway, e.g. "31L".
-- @param #AIRBASE self
-- @param #AIRBASE.Runway Runway The runway. Default is the active runway.
-- @param #boolean LongLeftRight If `true`, return "Left" or "Right" instead of "L" or "R".
-- @return #string Name of the runway or "XX" if it could not be found.
function AIRBASE:GetRunwayName(Runway, LongLeftRight)
Runway=Runway or self:GetActiveRunway()
local name="XX"
if Runway then
name=Runway.name
if Runway.isLeft==true then
if LongLeftRight then
name=name.." Left"
else
name=name.."L"
end
elseif Runway.isLeft==false then
if LongLeftRight then
name=name.." Right"
else
name=name.."R"
end
end
end
return name
end
--- Function that checks if at leat one unit of a group has been spawned close to a spawn point on the runway.
-- @param #AIRBASE self
-- @param Wrapper.Group#GROUP group Group to be checked.

View File

@ -1602,6 +1602,27 @@ function CONTROLLABLE:EnRouteTaskEngageTargetsInZone( Vec2, Radius, TargetTypes,
return DCSTask
end
--- (AIR) Enroute anti-ship task.
-- @param #CONTROLLABLE self
-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. Default `{"Ships"}`.
-- @param #number Priority (Optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. Default 0.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes, Priority)
local DCSTask = {
id = 'EngageTargets',
key = "AntiShip",
--auto = false,
--enabled = true,
params = {
targetTypes = TargetTypes or {"Ships"},
priority = Priority or 0
}
}
return DCSTask
end
--- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets.
-- @param #CONTROLLABLE self

View File

@ -187,6 +187,7 @@ GROUPTEMPLATE.Takeoff = {
-- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets.
-- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute.
-- @field #string GROUND_INFANTRY Ground infantry assets.
-- @field #string GROUND_IFV Ground Infantry Fighting Vehicle.
-- @field #string GROUND_ARTILLERY Artillery assets.
-- @field #string GROUND_TANK Tanks (modern or old).
-- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently.
@ -213,6 +214,7 @@ GROUP.Attribute = {
GROUND_APC="Ground_APC",
GROUND_TRUCK="Ground_Truck",
GROUND_INFANTRY="Ground_Infantry",
GROUND_IFV="Ground_IFV",
GROUND_ARTILLERY="Ground_Artillery",
GROUND_TANK="Ground_Tank",
GROUND_TRAIN="Ground_Train",
@ -2378,13 +2380,14 @@ function GROUP:GetAttribute()
--- Ground ---
--------------
-- Ground
local apc=self:HasAttribute("Infantry carriers")
local apc=self:HasAttribute("APC")
local truck=self:HasAttribute("Trucks") and self:GetCategory()==Group.Category.GROUND
local infantry=self:HasAttribute("Infantry")
local artillery=self:HasAttribute("Artillery")
local tank=self:HasAttribute("Old Tanks") or self:HasAttribute("Modern Tanks")
local aaa=self:HasAttribute("AAA")
local ewr=self:HasAttribute("EWR")
local ifv=self:HasAttribute("IFV")
local sam=self:HasAttribute("SAM elements") and (not self:HasAttribute("AAA"))
-- Train
local train=self:GetCategory()==Group.Category.TRAIN
@ -2432,6 +2435,8 @@ function GROUP:GetAttribute()
attribute=GROUP.Attribute.GROUND_APC
elseif infantry then
attribute=GROUP.Attribute.GROUND_INFANTRY
elseif ifv then
attribute=GROUP.Attribute.GROUND_IFV
elseif truck then
attribute=GROUP.Attribute.GROUND_TRUCK
elseif train then

View File

@ -322,6 +322,16 @@ function MARKER:ReadOnly()
return self
end
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadWrite()
self.readonly=false
return self
end
--- Set message that is displayed on screen if the marker is added.
-- @param #MARKER self
-- @param #string Text Message displayed when the marker is added.

View File

@ -30,6 +30,7 @@ Core/Timer.lua
Core/Goal.lua
Core/Spot.lua
Core/TextAndSound.lua
Core/Condition.lua
Wrapper/Object.lua
Wrapper/Identifiable.lua
@ -96,6 +97,8 @@ Ops/Chief.lua
Ops/CSAR.lua
Ops/CTLD.lua
Ops/Awacs.lua
Ops/Operation.lua
Ops/FlightControl.lua
AI/AI_Balancer.lua
AI/AI_Air.lua