mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
commit
420526df9d
295
Moose Development/Moose/Core/Condition.lua
Normal file
295
Moose Development/Moose/Core/Condition.lua
Normal 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
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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' )
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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" )
|
||||
|
||||
@ -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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -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
|
||||
|
||||
---------------------
|
||||
|
||||
@ -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).
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
|
||||
4578
Moose Development/Moose/Ops/FlightControl.lua
Normal file
4578
Moose Development/Moose/Ops/FlightControl.lua
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()
|
||||
|
||||
1163
Moose Development/Moose/Ops/Operation.lua
Normal file
1163
Moose Development/Moose/Ops/Operation.lua
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user