mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
2809 lines
98 KiB
Lua
2809 lines
98 KiB
Lua
--- **Ops** - AWACS
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Main Features:
|
|
--
|
|
-- * TBD
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Example Missions:
|
|
--
|
|
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/).
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **applevangelist**
|
|
-- Last Update April 2022
|
|
--
|
|
-- ===
|
|
-- @module Ops.AWACS
|
|
-- @image MOOSE.JPG
|
|
|
|
do
|
|
--- Ops AWACS Class
|
|
-- @type AWACS
|
|
-- @field #string ClassName Name of this class.
|
|
-- @field #string version Versioning.
|
|
-- @field #string lid LID for log entries.
|
|
-- @field #number coalition Colition side.
|
|
-- @field #string coalitiontxt = "blue"
|
|
-- @field Core.Zone#ZONE OpsZone,
|
|
-- @field Core.Zone#ZONE AnchorZone,
|
|
-- @field #number Frequency
|
|
-- @field #number Modulation
|
|
-- @field Wrapper.Airbase#AIRBASE Airbase
|
|
-- @field Ops.AirWing#AIRWING AirWing
|
|
-- @field #number AwacsAngels
|
|
-- @field Core.Zone#ZONE OrbitZone
|
|
-- @field #number CallSign
|
|
-- @field #number CallSignNo
|
|
-- @field #boolean debug
|
|
-- @field #number verbose
|
|
-- @field #table ManagedGrps
|
|
-- @field #number ManagedGrpID
|
|
-- @field #number ManagedTaskID
|
|
-- @field Utilities.FiFo#FIFO AnchorStacks
|
|
-- @field Utilities.FiFo#FIFO CAPIdleAI
|
|
-- @field Utilities.FiFo#FIFO CAPIdleHuman
|
|
-- @field Utilities.FiFo#FIFO TaskedCAPAI
|
|
-- @field Utilities.FiFo#FIFO TaskedCAPHuman
|
|
-- @field Utilities.FiFo#FIFO OpenTasks
|
|
-- @field Utilities.FiFo#FIFO AssignedTasks
|
|
-- @field Utilities.FiFo#FIFO PictureAO
|
|
-- @field Utilities.FiFo#FIFO PictureEWR
|
|
-- @field Utilities.FiFo#FIFO Contacts
|
|
-- @field Utilities.FiFo#FIFO ContactsAO
|
|
-- @field Utilities.FiFo#FIFO RadioQueue
|
|
-- @field Utilities.FiFo#FIFO CAPAirwings
|
|
-- @field #number AwacsTimeOnStation
|
|
-- @field #number AwacsTimeStamp
|
|
-- @field #number EscortsTimeOnStation
|
|
-- @field #number EscortsTimeStamp
|
|
-- @field #string AwacsROE
|
|
-- @field #string AwacsROT
|
|
-- @field Ops.Auftrag#AUFTRAG AwacsMission
|
|
-- @field Ops.Auftrag#AUFTRAG EscortMission
|
|
-- @field Ops.Auftrag#AUFTRAG AwacsMissionReplacement
|
|
-- @field Ops.Auftrag#AUFTRAG EscortMissionReplacement
|
|
-- @field Utilities.FiFo#FIFO AICAPMissions FIFO for Ops.Auftrag#AUFTRAG for AI CAP
|
|
-- @field #boolean MenuStrict
|
|
-- @field #number MaxAIonCAP
|
|
-- @field #number AIonCAP
|
|
-- @field #boolean ShiftChangeAwacsFlag
|
|
-- @field #boolean ShiftChangeEscortsFlag
|
|
-- @field #boolean ShiftChangeAwacsRequested
|
|
-- @field #boolean ShiftChangeEscortsRequested
|
|
-- @extends Core.Fsm#FSM
|
|
|
|
---
|
|
--
|
|
-- @field #AWACS
|
|
AWACS = {
|
|
ClassName = "AWACS", -- #string
|
|
version = "alpha 0.0.3", -- #string
|
|
lid = "", -- #string
|
|
coalition = coalition.side.BLUE, -- #number
|
|
coalitiontxt = "blue", -- #string
|
|
OpsZone = nil,
|
|
AnchorZone = nil,
|
|
AirWing = nil,
|
|
Frequency = 271, -- #number
|
|
Modulation = radio.modulation.AM, -- #number
|
|
Airbase = nil,
|
|
AwacsAngels = 25, -- orbit at 25'000 ft
|
|
OrbitZone = nil,
|
|
CallSign = CALLSIGN.AWACS.Magic, -- #number
|
|
CallSignNo = 1, -- #number
|
|
debug = true,
|
|
verbose = true,
|
|
ManagedGrps = {},
|
|
ManagedGrpID = 0, -- #number
|
|
ManagedTaskID = 0, -- #number
|
|
AnchorStacks = {}, -- Utilities.FiFo#FIFO
|
|
CAPIdleAI = {},
|
|
CAPIdleHuman = {},
|
|
TaskedCAPAI = {},
|
|
TaskedCAPHuman = {},
|
|
OpenTasks = {}, -- Utilities.FiFo#FIFO
|
|
AssignedTasks = {}, -- Utilities.FiFo#FIFO
|
|
PictureAO = {}, -- Utilities.FiFo#FIFO
|
|
PictureEWR = {}, -- Utilities.FiFo#FIFO
|
|
Contacts = {}, -- Utilities.FiFo#FIFO
|
|
ContactsAO = {}, -- Utilities.FiFo#FIFO
|
|
RadioQueue = {}, -- Utilities.FiFo#FIFO
|
|
AwacsTimeOnStation = 1,
|
|
AwacsTimeStamp = 0,
|
|
EscortsTimeOnStation = 0.5,
|
|
EscortsTimeStamp = 0,
|
|
AwacsROE = "",
|
|
AwacsROT = "",
|
|
MenuStrict = true,
|
|
MaxAIonCAP = 4,
|
|
AIonCAP = 0,
|
|
AICAPMissions = {}, -- Utilities.FiFo#FIFO
|
|
ShiftChangeAwacsFlag = false,
|
|
ShiftChangeEscortsFlag = false,
|
|
ShiftChangeAwacsRequested = false,
|
|
ShiftChangeEscortsRequested = false,
|
|
CAPAirwings = {}, -- Utilities.FiFo#FIFO
|
|
}
|
|
|
|
---
|
|
--@field CallSignClear
|
|
AWACS.CallSignClear = {
|
|
[1]="Overlord",
|
|
[2]="Magic",
|
|
[3]="Wizard",
|
|
[4]="Focus",
|
|
[5]="Darkstar",
|
|
}
|
|
|
|
---
|
|
-- @field AnchorNames
|
|
AWACS.AnchorNames = {
|
|
[1] = "One",
|
|
[2] = "Two",
|
|
[3] = "Three",
|
|
[4] = "Four",
|
|
[5] = "Five",
|
|
[6] = "Six",
|
|
[7] = "Seven",
|
|
[8] = "Eight",
|
|
[9] = "Nine",
|
|
[10] = "Ten",
|
|
}
|
|
|
|
---
|
|
-- @field ROE
|
|
AWACS.ROE = {
|
|
POLICE = "Police",
|
|
VID = "Visual ID",
|
|
IFF = "IFF",
|
|
BVR = "Beyond Visual Range",
|
|
}
|
|
|
|
---
|
|
-- @field AWACS.ROT
|
|
AWACS.ROT = {
|
|
PASSIVE = "Passive Defense",
|
|
ACTIVE = "Active Defense",
|
|
LOCK = "Lock",
|
|
RETURNFIRE = "Return Fire",
|
|
OPENFIRE = "Open Fire",
|
|
}
|
|
|
|
---
|
|
--@field THREATLEVEL -- can be 1-10, thresholds
|
|
AWACS.THREATLEVEL = {
|
|
GREEN = 3,
|
|
AMBER = 7,
|
|
RED = 10,
|
|
}
|
|
|
|
---
|
|
-- @type AWACS.MenuStructure
|
|
-- @field #boolean menuset
|
|
-- @field #string groupname
|
|
-- @field Core.Menu#MENU_GROUP basemenu
|
|
-- @field Core.Menu#MENU_GROUP_COMMAND checkin
|
|
-- @field Core.Menu#MENU_GROUP_COMMAND checkout
|
|
-- @field Core.Menu#MENU_GROUP_COMMAND picture
|
|
-- @field Core.Menu#MENU_GROUP_COMMAND bogeydope
|
|
-- @field Core.Menu#MENU_GROUP_COMMAND declare
|
|
-- @field Core.Menu#MENU_GROUP_COMMAND showtask
|
|
|
|
---
|
|
-- @type AWACS.ManagedGroup
|
|
-- @field Wrapper.Group#GROUP Group
|
|
-- @field #string GroupName
|
|
-- @field Ops.FlightGroup#FLIGHTGROUP FlightGroup for AI
|
|
-- @field #boolean IsPlayer
|
|
-- @field #boolean IsAI
|
|
-- @field #string CallSign
|
|
-- @field #number CurrentTask
|
|
-- @field #boolean HasAssignedTask
|
|
-- @field #number GID
|
|
-- @field #number AnchorStackNo
|
|
-- @field #number AnchorStackAngels
|
|
|
|
---
|
|
-- @type AWACS.TaskDescription
|
|
AWACS.TaskDescription = {
|
|
ANCHOR = "Anchor",
|
|
REANCHOR = "Re-Anchor",
|
|
INTERCEPT = "Intercept",
|
|
SWEEP = "Sweep",
|
|
RTB = "RTB",
|
|
}
|
|
|
|
---
|
|
-- @type AWACS.TaskStatus
|
|
AWACS.TaskStatus = {
|
|
IDLE = "Idle",
|
|
ASSIGNED = "Assigned",
|
|
EXECUTING = "Executing",
|
|
SUCCESS = "Success",
|
|
FAILED = "Failed",
|
|
}
|
|
|
|
---
|
|
-- @type AWACS.ManagedTask
|
|
-- @field #number TID
|
|
-- @field #number AssignedGroupID
|
|
-- @field #boolean IsPlayerTask
|
|
-- @field #boolean IsUnassigned
|
|
-- @field Ops.Target#TARGET Target
|
|
-- @field Ops.Auftrag#AUFTRAG Auftrag
|
|
-- @field #AWACS.TaskStatus Status
|
|
-- @field #AWACS.TaskDescription ToDo
|
|
-- @field #string ScreenText Long descrition
|
|
-- @field Ops.Intelligence#INTEL.Contact Contact
|
|
-- @field Ops.Intelligence#INTEL.Cluster Cluster
|
|
|
|
---
|
|
-- @type AWACS.AnchorAssignedEntry
|
|
-- @field #number ID
|
|
-- @field #number Angels
|
|
|
|
---
|
|
-- @type AWACS.AnchorData
|
|
-- @field #number AnchorBaseAngels
|
|
-- @field #boolean AnchorZone
|
|
-- @field Core.Point#COORDINATE AnchorZoneCoordinate
|
|
-- @field #boolean AnchorZoneCoordinateText
|
|
-- @field Utilities.FiFo#FIFO AnchorAssignedID FiFo of #AWACS.AnchorAssignedEntry
|
|
-- @field Utilities.FiFo#FIFO Anchors FiFo of available stacks
|
|
|
|
---
|
|
--@type RadioEntry
|
|
--@field #string TextTTS
|
|
--@field #string TextScreen
|
|
--@field #boolean IsNew
|
|
--@field #boolean IsGroup
|
|
--@field #boolean GroupID
|
|
--@field #number Duration
|
|
--@field #boolean ToScreen
|
|
--@field #boolean FromAI
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- TODO-List
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- DEBUG - Escorts via AirWing not staying on
|
|
-- TODO - Link (multiple) AWs to the AWACS Controller
|
|
-- DONE - Use AO as Anchor of Bulls, AO as default
|
|
-- DONE - SRS TTS output
|
|
-- DONE - Check-In/Out Humans
|
|
-- DONE - Check-In/Out AI
|
|
-- DONE - Picture
|
|
-- TODO - TripWire
|
|
-- DONE - Radio Menu
|
|
-- DONE - Intel Detection
|
|
-- TODO - CHIEF / COMMANDER / AIRWING connection?
|
|
-- TODO - LotATC / IFF
|
|
-- DONE - ROE
|
|
-- TODO - Player & AI tasking
|
|
-- DONE - Anchor Stack Management
|
|
-- TODO - Reporting
|
|
-- TODO - Missile launch callout
|
|
-- TODO - Localization
|
|
-- DONE - Shift Length AWACS/AI
|
|
-- DEBUG - Shift Change, Change on asset RTB or dead or mission done
|
|
-- TODO - Borders for INTEL
|
|
-- TODO - FIFO for checkin/checkout and tasking
|
|
-- TODO - Event detection
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Constructor
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
-- TODO Constructor
|
|
|
|
--- Set up a new AI AWACS.
|
|
-- @param #AWACS self
|
|
-- @param #string Name Name of this AWACS for the radio menu.
|
|
-- @param #string AirWing The core Ops.AirWing#AIRWING managing the AWACS, Escort and (optionally) AI CAP planes for us.
|
|
-- @param #number Coalition Coalition, e.g. coalition.side.BLUE. Can also be passed as "blue", "red" or "neutral".
|
|
-- @param #string AirbaseName Name of the home airbase.
|
|
-- @param #string AwacsOrbit Name of the round, mission editor created zone where this AWACS orbits.
|
|
-- @param #string OpsZone Name of the round, mission editor created operations zone this AWACS controls. Can be passed as #ZONE_POLYGON
|
|
-- @param #string AnchorZone Name of the round, mission editor created anchor zone where CAP groups will be stacked.
|
|
-- @param #number Frequency Radio frequency, e.g. 271.
|
|
-- @param #number Modulation Radio modulation, e.g. radio.modulation.AM or radio.modulation.FM.
|
|
-- @return #AWACS self
|
|
function AWACS:New(Name,AirWing,Coalition,AirbaseName,AwacsOrbit,OpsZone,AnchorZone,Frequency,Modulation)
|
|
-- Inherit everything from FSM class.
|
|
local self=BASE:Inherit(self, FSM:New())
|
|
|
|
--set Coalition
|
|
if Coalition and type(Coalition)=="string" then
|
|
if Coalition=="blue" then
|
|
self.coalition=coalition.side.BLUE
|
|
self.coalitiontxt = Coalition
|
|
elseif Coalition=="red" then
|
|
self.coalition=coalition.side.RED
|
|
self.coalitiontxt = Coalition
|
|
elseif Coalition=="neutral" then
|
|
self.coalition=coalition.side.NEUTRAL
|
|
self.coalitiontxt = Coalition
|
|
else
|
|
self:E("ERROR: Unknown coalition in AWACS!")
|
|
end
|
|
else
|
|
self.coalition = Coalition
|
|
self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition))
|
|
end
|
|
|
|
-- base setup
|
|
self.Name = Name -- #string
|
|
self.AirWing = AirWing -- Ops.AirWing#AIRWING object
|
|
|
|
AirWing:SetUsingOpsAwacs(self)
|
|
|
|
self.CAPAirwings = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.CAPAirwings:Push(AirWing,1)
|
|
|
|
self.AwacsFG = nil
|
|
--self.AwacsPayload = PayLoad -- Ops.AirWing#AIRWING.Payload
|
|
self.ModernEra = true -- use of EPLRS
|
|
self.RadarBlur = 10 -- 10% detection precision i.e. 90-110 reported group size
|
|
if type(OpsZone) == "string" then
|
|
self.OpsZone = ZONE:New(OpsZone) -- Core.Zone#ZONE
|
|
elseif type(OpsZone) == "table" and OpsZone.ClassName and OpsZone.ClassName == "ZONE_POLYGON" then
|
|
self.OpsZone = OpsZone
|
|
else
|
|
self:E("AWACS - Invalid OpsZone passed!")
|
|
return
|
|
end
|
|
|
|
self.AOCoordinate = self.OpsZone:GetCoordinate()
|
|
self.UseBullsAO = false
|
|
self.ControlZoneRadius = 100 -- nm
|
|
self.AnchorZone = ZONE:New(AnchorZone) -- Core.Zone#ZONE
|
|
self.Frequency = Frequency or 271 -- #number
|
|
self.Modulation = Modulation or radio.modulation.AM
|
|
self.Airbase = AIRBASE:FindByName(AirbaseName)
|
|
self.AwacsAngels = 25 -- orbit at 25'000 ft
|
|
self.OrbitZone = ZONE:New(AwacsOrbit) -- Core.Zone#ZONE
|
|
self.CallSign = CALLSIGN.AWACS.Magic -- #number
|
|
self.CallSignNo = 1 -- #number
|
|
self.NoHelos = true
|
|
self.MaxAIonCAP = 4
|
|
self.AIonCAP = 0
|
|
self.AICAPMissions = FIFO:New() -- Utilities.FiFo#FIFO
|
|
|
|
local speed = 250
|
|
self.SpeedBase = speed
|
|
self.Speed = UTILS.KnotsToAltKIAS(speed,self.AwacsAngels*1000)
|
|
self.CapSpeedBase = 220
|
|
self.Heading = 0 -- north
|
|
self.Leg = 50 -- nm
|
|
self.invisible = true
|
|
self.immortal = true
|
|
self.callsigntxt = "AWACS"
|
|
|
|
self.AwacsTimeOnStation = 2
|
|
self.AwacsTimeStamp = 0
|
|
self.EscortsTimeOnStation = 2
|
|
self.EscortsTimeStamp = 0
|
|
self.ShiftChangeTime = 0.25 -- 15mins
|
|
self.ShiftChangeAwacsFlag = false
|
|
self.ShiftChangeEscortsFlag = false
|
|
|
|
self.AwacsROE = AWACS.ROE.POLICE
|
|
self.AwacsROT = AWACS.ROT.PASSIVE
|
|
|
|
self.MenuStrict = true
|
|
|
|
-- Escorts
|
|
self.HasEscorts = false
|
|
self.EscortTemplate = ""
|
|
|
|
-- SRS
|
|
self.PathToSRS = "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
|
self.Gender = "male"
|
|
self.Culture = "en-US"
|
|
self.Voice = nil
|
|
self.Port = 5002
|
|
self.RadioQueue = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.maxspeakentries = 3
|
|
|
|
self.CAPGender = "male"
|
|
self.CAPCulture = "en-US"
|
|
self.CAPVoice = nil
|
|
|
|
-- Client SET
|
|
self.clientset = SET_GROUP:New():FilterCategoryAirplane():FilterCoalitions(self.coalitiontxt):FilterStart()
|
|
|
|
-- managed groups
|
|
self.ManagedGrps = {} -- #table of #AWACS.ManagedGroup entries
|
|
self.ManagedGrpID = 0
|
|
|
|
self.AICAPCAllName = CALLSIGN.F16.Viper
|
|
self.AICAPCAllNumber = 0
|
|
|
|
-- Anchor stacks init
|
|
self.AnchorStacks = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.AnchorBaseAngels = 22
|
|
self.AnchorStackDistance = 2
|
|
self.AnchorMaxStacks = 4
|
|
self.AnchorMaxAnchors = 2
|
|
self.AnchorMaxZones = 6
|
|
self.AnchorCurrZones = 1
|
|
self.AnchorTurn = -(360/self.AnchorMaxZones)
|
|
|
|
self:_CreateAnchorStack()
|
|
|
|
-- Task lists
|
|
self.AssignedTasks = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.OpenTasks = FIFO:New() -- Utilities.FiFo#FIFO
|
|
|
|
-- Pilot lists
|
|
--[[ ToDo - Maybe only 2? Move to managedgroups
|
|
self.CAPIdleAI = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.CAPIdleHuman = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.TaskedCAPAI = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.TaskedCAPHuman = FIFO:New() -- Utilities.FiFo#FIFO
|
|
--]]
|
|
|
|
-- Picture, Contacts, Bogeys
|
|
self.PictureAO = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.PictureEWR = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.Contacts = FIFO:New() -- Utilities.FiFo#FIFO
|
|
self.ContactsAO = FIFO:New() -- Utilities.FiFo#FIFO
|
|
|
|
-- SET for Intel Detection
|
|
self.DetectionSet=SET_GROUP:New()
|
|
|
|
-- Set some string id for output to DCS.log file.
|
|
self.lid=string.format("%s (%s) | ", self.Name, self.coalition and UTILS.GetCoalitionName(self.coalition) or "unknown")
|
|
|
|
-- Start State.
|
|
self:SetStartState("Stopped")
|
|
|
|
-- Add FSM transitions.
|
|
-- From State --> Event --> To State
|
|
self:AddTransition("Stopped", "Start", "Running") -- Start FSM.
|
|
self:AddTransition("*", "Status", "*") -- Status update.
|
|
self:AddTransition("*", "CheckedIn", "*")
|
|
self:AddTransition("*", "CheckedOut", "*")
|
|
self:AddTransition("*", "AssignAnchor", "*")
|
|
self:AddTransition("*", "AssignedAnchor", "*")
|
|
self:AddTransition("*", "NewCluster", "*")
|
|
self:AddTransition("*", "NewContact", "*")
|
|
self:AddTransition("*", "LostCluster", "*")
|
|
self:AddTransition("*", "LostContact", "*")
|
|
self:AddTransition("*", "CheckRadioQueue", "*")
|
|
self:AddTransition("*", "EscortShiftChange", "*")
|
|
self:AddTransition("*", "AwacsShiftChange", "*")
|
|
self:AddTransition("*", "FlightOnMission", "*")
|
|
--
|
|
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
|
|
|
-- self:__Start(math.random(2,5))
|
|
|
|
local text = string.format("%sAWACS Version %s Initiated",self.lid,self.version)
|
|
|
|
self:I(text)
|
|
|
|
-- debug zone markers
|
|
if self.debug then
|
|
self.OpsZone:DrawZone(-1,{1,0,0},1,{1,0,0},0.2,5,true)
|
|
MARKER:New(self.OpsZone:GetCoordinate(),"AO Zone"):ToAll()
|
|
self.AnchorZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true)
|
|
MARKER:New(self.AnchorZone:GetCoordinate(),"Anchor Zone"):ToAll()
|
|
self.OrbitZone:DrawZone(-1,{0,1,0},1,{0,1,0},0.2,5,true)
|
|
MARKER:New(self.OrbitZone:GetCoordinate(),"Orbit Zone"):ToAll()
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
-- TODO Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- [User] Get AWACS Name
|
|
-- @param #AWACS self
|
|
-- @return #string Name of this instance
|
|
function AWACS:GetName()
|
|
return self.Name or "not set"
|
|
end
|
|
|
|
--- [User] Set AWACS flight details
|
|
-- @param #AWACS self
|
|
-- @param #number CallSign Defaults to CALLSIGN.AWACS.Magic
|
|
-- @param #number CallSignNo Defaults to 1
|
|
-- @param #number Angels Defaults to 25 (i.e. 25000 ft)
|
|
-- @param #number Speed Defaults to 250kn
|
|
-- @param #number Heading Defaults to 0 (North)
|
|
-- @param #number Leg Defaults to 25nm
|
|
-- @return #AWACS self
|
|
function AWACS:SetAwacsDetails(CallSign,CallSignNo,Angels,Speed,Heading,Leg)
|
|
self:T(self.lid.."SetAwacsDetails")
|
|
self.CallSign = CallSign or CALLSIGN.AWACS.Magic
|
|
self.CallSignNo = CallSignNo or 1
|
|
self.Angels = Angels or 25
|
|
local speed = Speed or 250
|
|
self.SpeedBase = speed
|
|
self.Speed = UTILS.KnotsToAltKIAS(speed,self.Angels*1000)
|
|
self.Heading = Heading or 0
|
|
self.Leg = Leg or 25
|
|
return self
|
|
end
|
|
|
|
--- [User] Add a radar GROUP object to the INTEL detection SET_GROUP
|
|
-- @param Wrapper.Group#GROUP Group The GROUP to be added. Can be passed as SET_GROUP.
|
|
-- @return #AWACS self
|
|
function AWACS:AddGroupToDetection(Group)
|
|
self:T(self.lid.."AddGroupToDetection")
|
|
if Group and Group.ClassName and Group.ClassName == "GROUP" then
|
|
self.DetectionSet:AddGroup(Group)
|
|
elseif Group and Group.ClassName and Group.ClassName == "SET_GROUP" then
|
|
self.DetectionSet:AddSet(Group)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [User] Set AWACS SRS TTS details - see @{#MSRS} for details
|
|
-- @param #AWACS self
|
|
-- @param #string PathToSRS Defaults to "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
|
-- @param #string Gender Defaults to "male"
|
|
-- @param #string Culture Defaults to "en-US"
|
|
-- @param #number Port Defaults to 5002
|
|
-- @param #string Voice (Optional) Use a specifc voice with the @{#MSRS.SetVoice} function, e.g, `:SetVoice("Microsoft Hedda Desktop")`.
|
|
-- Note that this must be installed on your windows system.
|
|
-- @return #AWACS self
|
|
function AWACS:SetSRS(PathToSRS,Gender,Culture,Port,Voice)
|
|
self:T(self.lid.."SetSRS")
|
|
self.PathToSRS = PathToSRS or "C:\\Program Files\\DCS-SimpleRadio-Standalone"
|
|
self.Gender = Gender or "male"
|
|
self.Culture = Culture or "en-US"
|
|
self.Port = Port or 5002
|
|
self.Voice = Voice
|
|
return self
|
|
end
|
|
|
|
--- [User] Set AWACS Escorts Template
|
|
-- @param #AWACS self
|
|
-- @param #number EscortNumber Number of fighther planes to accompany this AWACS. 0 or nil means no escorts.
|
|
-- @return #AWACS self
|
|
function AWACS:SetEscort(EscortNumber)
|
|
self:T(self.lid.."SetEscort")
|
|
if EscortNumber and EscortNumber > 0 then
|
|
self.HasEscorts = true
|
|
self.EscortNumber = EscortNumber
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Start AWACS Escorts FlightGroup
|
|
-- @param #AWACS self
|
|
-- @param #boolean Shiftchange This is a shift change call
|
|
-- @return #AWACS self
|
|
function AWACS:_StartEscorts(Shiftchange)
|
|
self:T(self.lid.."_StartEscorts")
|
|
|
|
local AwacsFG = self.AwacsFG -- Ops.FlightGroup#FLIGHTGROUP
|
|
local group = AwacsFG:GetGroup()
|
|
local mission = AUFTRAG:NewESCORT(group,{x=-100, y=0, z=200},30,{"Air"})
|
|
mission:SetRequiredAssets(self.EscortNumber)
|
|
|
|
local timeonstation = (self.EscortsTimeOnStation + self.ShiftChangeTime) * 3600 -- hours to seconds
|
|
mission:SetTime(nil,timeonstation)
|
|
|
|
self.AirWing:AddMission(mission)
|
|
|
|
if Shiftchange then
|
|
self.EscortMissionReplacement = mission
|
|
else
|
|
self.EscortMission = mission
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS further Start Settings
|
|
-- @param #AWACS self
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup
|
|
-- @param Ops.Auftrag#AUFTRAG Mission
|
|
-- @return #AWACS self
|
|
function AWACS:_StartSettings(FlightGroup,Mission)
|
|
self:T(self.lid.."_StartSettings")
|
|
|
|
-- called by AW OnafterFlightOnMission(...)
|
|
|
|
local Mission = Mission -- Ops.Auftrag#AUFTRAG
|
|
local AwacsFG = FlightGroup -- Ops.FlightGroup#FLIGHTGROUP
|
|
|
|
-- Is this our Awacs mission?
|
|
if self.AwacsMission:GetName() == Mission:GetName() then
|
|
|
|
AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false)
|
|
AwacsFG:SwitchRadio(self.Frequency,self.Modulation)
|
|
AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000)
|
|
AwacsFG:SetHomebase(self.Airbase)
|
|
AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo)
|
|
AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold)
|
|
AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN)
|
|
AwacsFG:SetDefaultEPLRS(self.ModernEra)
|
|
AwacsFG:SetDespawnAfterLanding()
|
|
AwacsFG:SetFuelLowRTB(true)
|
|
AwacsFG:SetFuelLowThreshold(20)
|
|
|
|
local group = AwacsFG:GetGroup() -- Wrapper.Group#GROUP
|
|
|
|
group:SetCommandInvisible(self.invisible)
|
|
group:SetCommandImmortal(self.immortal)
|
|
group:CommandSetCallsign(self.CallSign,self.CallSignNo,2)
|
|
-- Non AWACS does not seem take AWACS CS in DCS Group
|
|
--group:CommandSetCallsign(CALLSIGN.Aircraft.Pig,self.CallSignNo,2)
|
|
|
|
AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil)
|
|
self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo)
|
|
|
|
local text = string.format("%s starting for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO")
|
|
self:T(self.lid..text)
|
|
|
|
AwacsFG:RadioTransmission(text,1,false)
|
|
|
|
self.AwacsFG = AwacsFG
|
|
|
|
self:__CheckRadioQueue(10)
|
|
|
|
if self.HasEscorts then
|
|
--mission:SetRequiredEscorts(self.EscortNumber)
|
|
self:_StartEscorts()
|
|
end
|
|
|
|
self.AwacsTimeStamp = timer.getTime()
|
|
self.EscortsTimeStamp = timer.getTime()
|
|
|
|
elseif self.ShiftChangeAwacsRequested and self.AwacsMissionReplacement and self.AwacsMissionReplacement:GetName() == Mission:GetName() then
|
|
|
|
-- manage AWACS Replacement
|
|
AwacsFG:SetDefaultRadio(self.Frequency,self.Modulation,false)
|
|
AwacsFG:SwitchRadio(self.Frequency,self.Modulation)
|
|
AwacsFG:SetDefaultAltitude(self.AwacsAngels*1000)
|
|
AwacsFG:SetHomebase(self.Airbase)
|
|
self.CallSignNo = self.CallSignNo+1
|
|
AwacsFG:SetDefaultCallsign(self.CallSign,self.CallSignNo)
|
|
AwacsFG:SetDefaultROE(ENUMS.ROE.WeaponHold)
|
|
AwacsFG:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN)
|
|
AwacsFG:SetDefaultEPLRS(self.ModernEra)
|
|
AwacsFG:SetDespawnAfterLanding()
|
|
AwacsFG:SetFuelLowRTB(true)
|
|
AwacsFG:SetFuelLowThreshold(20)
|
|
|
|
local group = AwacsFG:GetGroup() -- Wrapper.Group#GROUP
|
|
|
|
group:SetCommandInvisible(self.invisible)
|
|
group:SetCommandImmortal(self.immortal)
|
|
group:CommandSetCallsign(self.CallSign,self.CallSignNo,2)
|
|
-- Non AWACS does not seem take AWACS CS in DCS Group
|
|
-- group:CommandSetCallsign(CALLSIGN.Aircraft.Pig,self.CallSignNo,2)
|
|
|
|
AwacsFG:SetSRS(self.PathToSRS,self.Gender,self.Culture,self.Voice,self.Port,nil)
|
|
self.callsigntxt = string.format("%s %d %d",AWACS.CallSignClear[self.CallSign],1,self.CallSignNo)
|
|
|
|
local text = string.format("%s shift change for AO %s control.",self.callsigntxt,self.OpsZone:GetName() or "AO")
|
|
self:T(self.lid..text)
|
|
|
|
AwacsFG:RadioTransmission(text,1,false)
|
|
|
|
self.AwacsFG = AwacsFG
|
|
|
|
--self:__CheckRadioQueue(10)
|
|
|
|
if self.HasEscorts then
|
|
--mission:SetRequiredEscorts(self.EscortNumber)
|
|
self:_StartEscorts(true)
|
|
end
|
|
|
|
self.AwacsTimeStamp = timer.getTime()
|
|
self.EscortsTimeStamp = timer.getTime()
|
|
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Check if a group has checked in
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to check
|
|
-- @return #number ID
|
|
-- @return #boolean CheckedIn
|
|
function AWACS:_GetManagedGrpID(Group)
|
|
self:T(self.lid.."_GetManagedGrpID for "..Group:GetName())
|
|
local GID = 0
|
|
local Outcome = false
|
|
local nametocheck = Group:GetName()
|
|
local managedgrps = self.ManagedGrps or {}
|
|
for _,_managed in pairs (managedgrps) do
|
|
local managed = _managed -- #AWACS.ManagedGroup
|
|
if managed.GroupName == nametocheck then
|
|
GID = managed.GID
|
|
Outcome = true
|
|
end
|
|
end
|
|
return GID, Outcome
|
|
end
|
|
|
|
--- [Internal] AWACS Get TTS compatible callsign
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #string Callsign
|
|
function AWACS:_GetCallSign(Group)
|
|
self:T(self.lid.."_GetCallSign")
|
|
local callsign = ""
|
|
local shortcallsign = Group:GetCallsign() -- e.g.Uzi11, but we want Uzi 1 1
|
|
local callnumber = string.match(shortcallsign, "(%d+)$" )
|
|
local callnumbermajor = string.char(string.byte(callnumber,1))
|
|
local callnumberminor = string.char(string.byte(callnumber,2))
|
|
callsign = string.gsub(shortcallsign,callnumber,"").." "..callnumbermajor.." "..callnumberminor
|
|
self:T("Callsign for TTS = " .. callsign)
|
|
return callsign
|
|
end
|
|
|
|
--- [Internal] AWACS Speak Picture AO/EWR entries
|
|
-- @param #AWACS self
|
|
-- @param #boolean AO If true this is for AO, else EWR
|
|
-- @param #string Callsign Callsign to address
|
|
-- @param #number GID GroupID for comms
|
|
-- @return #AWACS self
|
|
function AWACS:_CreatePicture(AO,Callsign,GID)
|
|
self:T(self.lid.."_CreatePicture AO="..tostring(AO).." for "..Callsign.." GID "..GID)
|
|
|
|
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
local group = managedgroup.Group -- Wrapper.Group#GROUP
|
|
local groupcoord = group:GetCoordinate()
|
|
|
|
local fifo = self.PictureAO -- Utilities.FiFo#FIFO
|
|
local maxentries = self.maxspeakentries
|
|
local counter = 0
|
|
|
|
if not AO then
|
|
fifo = self.PictureEWR
|
|
end
|
|
|
|
local entries = fifo:GetSize()
|
|
|
|
if entries < maxentries then maxentries = entries end
|
|
|
|
local text = "First group."
|
|
local textScreen = text
|
|
|
|
while counter < maxentries do
|
|
counter = counter + 1
|
|
local cluster = fifo:Pull() -- Ops.Intelligence#INTEL.Cluster
|
|
self:T({cluster})
|
|
if cluster and cluster.coordinate then
|
|
local clustercoord = cluster.coordinate -- Core.Point#COORDINATE
|
|
local refBRAA = clustercoord:ToStringBRA(groupcoord)
|
|
text = text .. " "..refBRAA.."."
|
|
textScreen = textScreen .." "..refBRAA..".\n"
|
|
if counter < maxentries then
|
|
text = text .. " Next group."
|
|
textScreen = textScreen .. text .. "\n"
|
|
end
|
|
end
|
|
end
|
|
|
|
-- empty queue from leftovers
|
|
fifo:Clear()
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = managedgroup.IsPlayer
|
|
RadioEntry.ToScreen = managedgroup.IsPlayer
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Speak Bogey Dope entries
|
|
-- @param #AWACS self
|
|
-- @param #string Callsign Callsign to address
|
|
-- @param #number GID GroupID for comms
|
|
-- @return #AWACS self
|
|
function AWACS:_CreateBogeyDope(Callsign,GID)
|
|
self:T(self.lid.."_CreateBogeyDope for "..Callsign.." GID "..GID)
|
|
|
|
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
local group = managedgroup.Group -- Wrapper.Group#GROUP
|
|
local groupcoord = group:GetCoordinate()
|
|
|
|
local fifo = self.ContactsAO -- Utilities.FiFo#FIFO
|
|
local maxentries = self.maxspeakentries
|
|
local counter = 0
|
|
|
|
local entries = fifo:GetSize()
|
|
|
|
if entries < maxentries then maxentries = entries end
|
|
|
|
local sortedIDs = fifo:GetIDStackSorted() -- sort by distance
|
|
|
|
while counter < maxentries do
|
|
counter = counter + 1
|
|
local cluster = fifo:PullByID(sortedIDs[counter]) -- Ops.Intelligence#INTEL.Contact
|
|
self:T({cluster})
|
|
if cluster and cluster.position then
|
|
self:_AnnounceContact(cluster,false,group,true)
|
|
end
|
|
end
|
|
|
|
-- empty queue from leftovers
|
|
fifo:Clear()
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Menu for Picture
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #AWACS self
|
|
function AWACS:_Picture(Group)
|
|
self:T(self.lid.."_Picture")
|
|
local text = "Picture WIP"
|
|
local textScreen = text
|
|
|
|
if not self.intel then
|
|
-- no intel yet!
|
|
text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
textScreen = text
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
return self
|
|
end
|
|
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
|
|
if Outcome then
|
|
-- Pilot is checked in
|
|
-- get clusters from Intel
|
|
local clustertable = self.intel:GetClusterTable()
|
|
-- sort into buckets
|
|
for _,_cluster in pairs(clustertable) do
|
|
local cluster = _cluster -- Ops.Intelligence#INTEL.Cluster
|
|
local coordVec2 = cluster.coordinate:GetVec2()
|
|
|
|
if self.OpsZone:IsVec2InZone(coordVec2) then
|
|
self.PictureAO:Push(cluster)
|
|
elseif self.ControlZone:IsVec2InZone(coordVec2) then
|
|
self.PictureEWR:Push(cluster)
|
|
end
|
|
end
|
|
|
|
local clustersAO = self.PictureAO:GetSize()
|
|
local clustersEWR = self.PictureEWR:GetSize()
|
|
|
|
if clustersAO == 0 and clustersEWR == 0 then
|
|
-- clear
|
|
text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
textScreen = text
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
else
|
|
|
|
if clustersAO > 0 then
|
|
text = string.format("%s. %s. Picture A O. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
textScreen = string.format("%s. %s. Picture AO. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
if clustersAO == 1 then
|
|
text = text .. "One group. "
|
|
textScreen = textScreen .. "One group.\n"
|
|
else
|
|
text = text .. clustersAO .. " groups. "
|
|
textScreen = textScreen .. clustersAO .. " groups.\n"
|
|
end
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
self:_CreatePicture(true,self:_GetCallSign(Group) or "Unknown 1 1",GID)
|
|
end
|
|
|
|
if clustersEWR > 0 then
|
|
text = string.format("%s. %s. Picture Early Warning. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
textScreen = string.format("%s. %s. Picture EWR. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
if clustersEWR == 1 then
|
|
text = text .. "One group. "
|
|
textScreen = textScreen .. "One group.\n"
|
|
else
|
|
text = text .. clustersEWR .. " groups. "
|
|
textScreen = textScreen .. clustersAO .. " groups.\n"
|
|
end
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
self:_CreatePicture(false,self:_GetCallSign(Group) or "Unknown 1 1",GID)
|
|
end
|
|
end
|
|
|
|
elseif self.AwacsFG then
|
|
-- no, unknown
|
|
text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Menu for Bogey Dope
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #AWACS self
|
|
function AWACS:_BogeyDope(Group)
|
|
self:T(self.lid.."_BogeyDope")
|
|
local text = "BogeyDope WIP"
|
|
local textScreen = "BogeyDope WIP"
|
|
|
|
if not self.intel then
|
|
-- no intel yet!
|
|
text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
textScreen = text
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = 0
|
|
RadioEntry.IsGroup = false
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
return self
|
|
end
|
|
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
|
|
if Outcome then
|
|
-- Pilot is checked in
|
|
|
|
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
local pilotgroup = managedgroup.Group
|
|
local pilotcoord = managedgroup.Group:GetCoordinate()
|
|
|
|
-- get contacts from Intel
|
|
local contactstable = self.intel:GetContactTable()
|
|
|
|
-- sort into buckets - AO only for bogey dope!
|
|
for _,_contact in pairs(contactstable) do
|
|
local cluster = _contact -- Ops.Intelligence#INTEL.Contact
|
|
local coordVec2 = cluster.position:GetVec2()
|
|
|
|
-- Get distance for sorting
|
|
local dist = pilotcoord:Get2DDistance(cluster.position)
|
|
|
|
if self.OpsZone:IsVec2InZone(coordVec2) then
|
|
self.ContactsAO:Push(cluster,dist)
|
|
end
|
|
end
|
|
|
|
local contactsAO = self.ContactsAO:GetSize()
|
|
|
|
if contactsAO == 0 then
|
|
-- clear
|
|
text = string.format("%s. %s. Clear.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
textScreen = text
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = Outcome
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
else
|
|
|
|
if contactsAO > 0 then
|
|
text = string.format("%s. %s. Bogey Dope. ",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
if contactsAO == 1 then
|
|
text = text .. "One group. "
|
|
textScreen = text .. "\n"
|
|
else
|
|
text = text .. contactsAO .. " groups. "
|
|
textScreen = text .. contactsAO .. " groups.\n"
|
|
end
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
self:_CreateBogeyDope(self:_GetCallSign(Group) or "Unknown 1 1",GID)
|
|
end
|
|
end
|
|
|
|
elseif self.AwacsFG then
|
|
-- no, unknown
|
|
text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
end
|
|
return self
|
|
end
|
|
|
|
|
|
--- [Internal] AWACS Menu for Declare
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #AWACS self
|
|
function AWACS:_Declare(Group)
|
|
self:T(self.lid.."_Declare")
|
|
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
local text = "Declare Not yet implemented"
|
|
if Outcome then
|
|
--[[ yes, known
|
|
|
|
--]]
|
|
elseif self.AwacsFG then
|
|
-- no, unknown
|
|
text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
end
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Menu for Showtask
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #AWACS self
|
|
function AWACS:_Showtask(Group)
|
|
self:T(self.lid.."_Showtask")
|
|
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
local text = "Showtask WIP"
|
|
|
|
if Outcome then
|
|
-- known group
|
|
|
|
-- Do we have a task?
|
|
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
|
|
if managedgroup.IsPlayer and self.TaskedCAPHuman:HasUniqueID(GID) then
|
|
|
|
if managedgroup.CurrentTask >0 and self.AssignedTasks:HasUniqueID(managedgroup.CurrentTask) then
|
|
-- get task structure
|
|
local currenttask = self.AssignedTasks:ReadByID(managedgroup.CurrentTask) -- #AWACS.ManagedTask
|
|
if currenttask then
|
|
local status = currenttask.Status
|
|
local targettype = currenttask.Target:GetCategory()
|
|
local targetstatus = currenttask.Target:GetState()
|
|
local ToDo = currenttask.ToDo
|
|
local description = currenttask.ScreenText
|
|
local callsign = self:_GetCallSign(Group)
|
|
|
|
if self.debug then
|
|
local taskreport = REPORT:New("AWACS Tasking Display")
|
|
taskreport:Add("===============")
|
|
taskreport:Add(string.format("Task for Callsign: %s",callsign))
|
|
taskreport:Add(string.format("Task: %s with Status: %s",ToDo,status))
|
|
taskreport:Add(string.format("Target of Type: %s",targettype))
|
|
taskreport:Add(string.format("Target in State: %s",targetstatus))
|
|
taskreport:Add("===============")
|
|
self:I(taskreport:Text())
|
|
end
|
|
|
|
MESSAGE:New(description,30,"AWACS",true):ToGroup(Group)
|
|
|
|
end
|
|
end
|
|
end
|
|
|
|
elseif self.AwacsFG then
|
|
-- no, unknown
|
|
text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = Outcome
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Menu for Check in
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #AWACS self
|
|
function AWACS:_CheckIn(Group)
|
|
self:I(self.lid.."_CheckIn "..Group:GetName())
|
|
-- check if already known
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
local text = ""
|
|
if not Outcome then
|
|
self.ManagedGrpID = self.ManagedGrpID + 1
|
|
local managedgroup = {} -- #AWACS.ManagedGroup
|
|
managedgroup.Group = Group
|
|
managedgroup.GroupName = Group:GetName()
|
|
managedgroup.IsPlayer = true
|
|
managedgroup.IsAI = false
|
|
managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1"
|
|
managedgroup.CurrentTask = 0
|
|
managedgroup.HasAssignedTask = false
|
|
managedgroup.GID = self.ManagedGrpID
|
|
GID = managedgroup.GID
|
|
self.ManagedGrps[self.ManagedGrpID]=managedgroup
|
|
text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign)
|
|
self:__CheckedIn(1,managedgroup.GID)
|
|
self:__AssignAnchor(5,managedgroup.GID)
|
|
elseif self.AwacsFG then
|
|
text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
end
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = true
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Menu for CheckInAI
|
|
-- @param #AWACS self
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup to use
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @param #number AuftragsNr Ops.Auftrag#AUFTRAG.auftragsnummer
|
|
-- @return #AWACS self
|
|
function AWACS:_CheckInAI(FlightGroup,Group,AuftragsNr)
|
|
self:I(self.lid.."_CheckInAI "..Group:GetName())
|
|
-- check if already known
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
local text = ""
|
|
if not Outcome then
|
|
self.ManagedGrpID = self.ManagedGrpID + 1
|
|
local managedgroup = {} -- #AWACS.ManagedGroup
|
|
managedgroup.Group = Group
|
|
managedgroup.GroupName = Group:GetName()
|
|
managedgroup.FlightGroup = FlightGroup
|
|
managedgroup.IsPlayer = false
|
|
managedgroup.IsAI = true
|
|
managedgroup.CallSign = self:_GetCallSign(Group) or "Unknown 1 1"
|
|
managedgroup.CurrentTask = AuftragsNr
|
|
managedgroup.HasAssignedTask = false
|
|
managedgroup.GID = self.ManagedGrpID
|
|
|
|
-- SRS voice for CAP
|
|
--FlightGroup:SetSRS(PathToSRS,Gender,Culture,Voice,Port,PathToGoogleKey)
|
|
|
|
FlightGroup:SetDefaultRadio(self.Frequency,self.Modulation,false)
|
|
FlightGroup:SwitchRadio(self.Frequency,self.Modulation)
|
|
|
|
FlightGroup:SetSRS(self.PathToSRS,self.CAPGender,self.CAPCulture,self.CAPVoice,self.Port,nil)
|
|
|
|
self.ManagedGrps[self.ManagedGrpID]=managedgroup
|
|
text = string.format("%s. Copy %s. Await tasking.",self.callsigntxt,managedgroup.CallSign)
|
|
self:__CheckedIn(1,managedgroup.GID)
|
|
self:__AssignAnchor(5,managedgroup.GID)
|
|
else
|
|
text = string.format("%s. Negative %s. You are already checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
end
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.ToScreen = false
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Menu for Check Out
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP Group Group to use
|
|
-- @return #AWACS self
|
|
function AWACS:_CheckOut(Group)
|
|
self:I(self.lid.."_CheckOut")
|
|
|
|
-- check if already known
|
|
local GID, Outcome = self:_GetManagedGrpID(Group)
|
|
local text = ""
|
|
if Outcome then
|
|
-- yes, known
|
|
text = string.format("%s. Copy %s. Have a safe flight home.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
-- grab some data before we nil the entry
|
|
local AnchorAssigned = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
local Stack = AnchorAssigned.AnchorStackNo
|
|
local Angels = AnchorAssigned.AnchorStackAngels
|
|
self.ManagedGrps[GID] = nil
|
|
self:__CheckedOut(1,GID,Stack,Angels)
|
|
else
|
|
-- no, unknown
|
|
text = string.format("%s. Negative %s. You are not checked in.",self.callsigntxt,self:_GetCallSign(Group) or "Unknown 1 1")
|
|
end
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS set client menus
|
|
-- @param #AWACS self
|
|
-- @return #AWACS self
|
|
function AWACS:_SetClientMenus()
|
|
self:T(self.lid.."_SetClientMenus")
|
|
local clientset = self.clientset -- Core.Set#SET_GROUP
|
|
local aliveset = clientset:GetAliveSet() -- #table of #GROUP objects
|
|
--local aliveobjects = aliveset:GetSetObjects() or {}
|
|
local clientmenus = {}
|
|
for _,_group in pairs(aliveset) do
|
|
-- go through set and build the menu
|
|
local grp = _group -- Wrapper.Group#GROUP
|
|
if self.MenuStrict then
|
|
-- check if pilot has checked in
|
|
if grp and grp:IsAlive() and grp:GetUnit(1):IsPlayer() then
|
|
local GID, checkedin = self:_GetManagedGrpID(grp)
|
|
if checkedin then
|
|
-- full menu minus checkin
|
|
local hasclientmenu = self.clientmenus[grp:GetName()] -- #AWACS.MenuStructure
|
|
--local checkinmenu = hasclientmenu.checkin -- Core.Menu#MENU_GROUP_COMMAND
|
|
--checkinmenu:Remove(nil,grp:GetName())
|
|
local basemenu = hasclientmenu.basemenu -- Core.Menu#MENU_GROUP
|
|
--local basemenu = MENU_GROUP:New(grp.Name,nil)
|
|
basemenu:RemoveSubMenus()
|
|
local picture = MENU_GROUP_COMMAND:New(grp,"Picture",basemenu,self._Picture,self,grp)
|
|
local bogeydope = MENU_GROUP_COMMAND:New(grp,"Bogey Dope",basemenu,self._BogeyDope,self,grp)
|
|
local declare = MENU_GROUP_COMMAND:New(grp,"Declare",basemenu,self._Declare,self,grp)
|
|
local showtask = MENU_GROUP_COMMAND:New(grp,"Showtask",basemenu,self._Showtask,self,grp)
|
|
local checkout = MENU_GROUP_COMMAND:New(grp,"Check Out",basemenu,self._CheckOut,self,grp):Refresh()
|
|
clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure
|
|
groupname = grp:GetName(),
|
|
menuset = true,
|
|
basemenu = basemenu,
|
|
--checkin = checkin,
|
|
checkout= checkout,
|
|
picture = picture,
|
|
bogeydope = bogeydope,
|
|
declare = declare,
|
|
showtask = showtask,
|
|
}
|
|
elseif not clientmenus[grp:GetName()] then
|
|
-- check in only
|
|
local basemenu = MENU_GROUP:New(grp,self.Name,nil)
|
|
local checkin = MENU_GROUP_COMMAND:New(grp,"Check In",basemenu,self._CheckIn,self,grp)
|
|
checkin:SetTag(grp:GetName())
|
|
checkin:Refresh()
|
|
clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure
|
|
groupname = grp:GetName(),
|
|
menuset = true,
|
|
basemenu = basemenu,
|
|
checkin = checkin,
|
|
--checkout= checkout,
|
|
--picture = picture,
|
|
--bogeydope = bogeydope,
|
|
--declare = declare,
|
|
--showtask = showtask,
|
|
}
|
|
end
|
|
end
|
|
else
|
|
if grp and grp:IsAlive() and grp:GetUnit(1):IsPlayer() and not clientmenus[grp:GetName()] then
|
|
local basemenu = MENU_COALITION:New(self.coalition,self.Name,nil)
|
|
local picture = MENU_COALITION_COMMAND:New(self.coalition,"Picture",basemenu,self._Picture,self,grp)
|
|
local bogeydope = MENU_COALITION_COMMAND:New(self.coalition,"Bogey Dope",basemenu,self._BogeyDope,self,grp)
|
|
local declare = MENU_COALITION_COMMAND:New(self.coalition,"Declare",basemenu,self._Declare,self,grp)
|
|
local showtask = MENU_COALITION_COMMAND:New(self.coalition,"Showtask",basemenu,self._Showtask,self,grp)
|
|
local checkin = MENU_COALITION_COMMAND:New(self.coalition,"Check In",basemenu,self._CheckIn,self,grp)
|
|
local checkout = MENU_COALITION_COMMAND:New(self.coalition,"Check Out",basemenu,self._CheckOut,self,grp):Refresh()
|
|
clientmenus[grp:GetName()] = { -- #AWACS.MenuStructure
|
|
groupname = grp:GetName(),
|
|
menuset = true,
|
|
basemenu = basemenu,
|
|
checkin = checkin,
|
|
checkout= checkout,
|
|
picture = picture,
|
|
bogeydope = bogeydope,
|
|
declare = declare,
|
|
showtask = showtask,
|
|
}
|
|
end
|
|
end
|
|
end
|
|
self.clientmenus = clientmenus
|
|
return self
|
|
end
|
|
|
|
--- [Internal] AWACS Create a new Anchor Stack
|
|
-- @param #AWACS self
|
|
-- @return #boolean success
|
|
-- @return #nunber AnchorStackNo
|
|
function AWACS:_CreateAnchorStack()
|
|
self:T(self.lid.."_CreateAnchorStack")
|
|
local stackscreated = self.AnchorStacks:GetSize()
|
|
if stackscreated == self.AnchorMaxAnchors then
|
|
-- only create self.AnchorMaxAnchors Anchors
|
|
return false, 0
|
|
end
|
|
local AnchorStackOne = {} -- #AWACS.AnchorData
|
|
AnchorStackOne.AnchorBaseAngels = self.AnchorBaseAngels
|
|
AnchorStackOne.Anchors = FIFO:New() -- Utilities.FiFo#FIFO
|
|
AnchorStackOne.AnchorAssignedID = FIFO:New() -- Utilities.FiFo#FIFO
|
|
local newname = ""
|
|
for i=1,self.AnchorMaxStacks do
|
|
AnchorStackOne.Anchors:Push((i-1)*self.AnchorStackDistance+self.AnchorBaseAngels)
|
|
end
|
|
if self.debug then
|
|
--AnchorStackOne.Anchors:Flush()
|
|
end
|
|
if stackscreated == 0 then
|
|
AnchorStackOne.AnchorZone = self.AnchorZone
|
|
AnchorStackOne.AnchorZoneCoordinate = self.AnchorZone:GetCoordinate()
|
|
AnchorStackOne.AnchorZoneCoordinateText = self.AnchorZone:GetCoordinate():ToStringLLDDM()
|
|
--push to AnchorStacks
|
|
self.AnchorStacks:Push(AnchorStackOne,"One")
|
|
else
|
|
local newsubname = AWACS.AnchorNames[stackscreated+1] or tostring(stackscreated+1)
|
|
newname = self.AnchorZone:GetName() .. "-"..newsubname
|
|
local anchorbasecoord = self.OpsZone:GetCoordinate() -- Core.Point#COORDINATE
|
|
-- OpsZone can be Polygon, so use distance to AnchorZone as radius
|
|
local anchorradius = anchorbasecoord:Get2DDistance(self.AnchorZone:GetCoordinate())
|
|
--local anchorradius = self.OpsZone:GetRadius() -- #number
|
|
--anchorradius = anchorradius + self.AnchorZone:GetRadius()
|
|
local angel = self.AnchorZone:GetCoordinate():GetAngleDegrees(self.OpsZone:GetVec3())
|
|
self:T("Angel Radians= " .. angel)
|
|
local turn = math.fmod(self.AnchorTurn*stackscreated,360) -- #number
|
|
if self.AnchorTurn < 0 then turn = -turn end
|
|
local newanchorbasecoord = anchorbasecoord:Translate(anchorradius,turn+angel) -- Core.Point#COORDINATE
|
|
AnchorStackOne.AnchorZone = ZONE_RADIUS:New(newname, newanchorbasecoord:GetVec2(), self.AnchorZone:GetRadius())
|
|
AnchorStackOne.AnchorZoneCoordinate = newanchorbasecoord
|
|
AnchorStackOne.AnchorZoneCoordinateText = newanchorbasecoord:ToStringLLDDM()
|
|
--push to AnchorStacks
|
|
self.AnchorStacks:Push(AnchorStackOne,newname)
|
|
end
|
|
|
|
if self.debug then
|
|
--self.AnchorStacks:Flush()
|
|
AnchorStackOne.AnchorZone:DrawZone(-1,{0,0,1},1,{0,0,1},0.2,5,true)
|
|
MARKER:New(AnchorStackOne.AnchorZone:GetCoordinate(),"Anchor Zone: "..newname):ToAll()
|
|
end
|
|
|
|
return true,self.AnchorStacks:GetSize()
|
|
|
|
end
|
|
|
|
--- [Internal] AWACS get free anchor stack for managed groups
|
|
-- @param #AWACS self
|
|
-- @return #number AnchorStackNo
|
|
-- @return #boolean free
|
|
function AWACS:_GetFreeAnchorStack()
|
|
self:T(self.lid.."_GetFreeAnchorStack")
|
|
local AnchorStackNo, Free = 0, false
|
|
--return AnchorStackNo, Free
|
|
local availablestacks = self.AnchorStacks:GetPointerStack() or {} -- #table
|
|
for _id,_entry in pairs(availablestacks) do
|
|
local entry = _entry -- Utilities.FiFo#FIFO.IDEntry
|
|
local data = entry.data -- #AWACS.AnchorData
|
|
if data.Anchors:IsNotEmpty() then
|
|
AnchorStackNo = _id
|
|
Free = true
|
|
break
|
|
end
|
|
end
|
|
-- TODO - if extension of anchor stacks to max, send AI home
|
|
if not Free then
|
|
-- try to create another stack
|
|
local created, number = self:_CreateAnchorStack()
|
|
if created then
|
|
-- we could create a new one - phew!
|
|
self:_GetFreeAnchorStack()
|
|
end
|
|
end
|
|
return AnchorStackNo, Free
|
|
end
|
|
|
|
--- [Internal] AWACS Assign Anchor Position to a Group
|
|
-- @param #AWACS self
|
|
-- @return #number GID Managed Group ID
|
|
-- @return #AWACS self
|
|
function AWACS:_AssignAnchorToID(GID)
|
|
self:T(self.lid.."_AssignAnchorToID")
|
|
local AnchorStackNo, Free = self:_GetFreeAnchorStack()
|
|
if Free then
|
|
-- get the Anchor from the stack
|
|
local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData
|
|
-- pull one free angels
|
|
local freeangels = Anchor.Anchors:Pull()
|
|
-- push GID on anchor
|
|
Anchor.AnchorAssignedID:Push(GID)
|
|
if self.debug then
|
|
--Anchor.AnchorAssignedID:Flush()
|
|
--Anchor.Anchors:Flush()
|
|
end
|
|
-- push back to AnchorStacks
|
|
self.AnchorStacks:Push(Anchor)
|
|
self:T({Anchor,freeangels})
|
|
self:__AssignedAnchor(5,GID,Anchor,AnchorStackNo,freeangels)
|
|
else
|
|
self:E(self.lid .. "Cannot assing free anchor stack to GID ".. GID)
|
|
-- try again ...
|
|
self:__AssignAnchor(10,GID)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Remove GID (group) from Anchor Stack
|
|
-- @param #AWACS self
|
|
-- @param #AWACS.ManagedGroup.GID ID
|
|
-- @param #number AnchorStackNo
|
|
-- @param #number Angels
|
|
-- @return #AWACS self
|
|
function AWACS:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels)
|
|
self:I(self.lid.."_RemoveIDFromAnchor for GID="..GID.." Stack="..AnchorStackNo.." Angels="..Angels)
|
|
-- pull correct anchor
|
|
local AnchorStackNo = AnchorStackNo or 1
|
|
local Anchor = self.AnchorStacks:PullByPointer(AnchorStackNo) -- #AWACS.AnchorData
|
|
-- pull GID from stack
|
|
local removedID = Anchor.AnchorAssignedID:PullByID(GID)
|
|
-- push free angels to stack
|
|
Anchor.Anchors:Push(Angels)
|
|
-- push back AnchorStack
|
|
self.AnchorStacks:Push(Anchor)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Start INTEL detection when we reach the AWACS Orbit Zone
|
|
-- @param #AWACS self
|
|
-- @param Wrapper.Group#GROUP awacs
|
|
-- @return #AWACS self
|
|
function AWACS:_StartIntel(awacs)
|
|
self:T(self.lid.."_StartIntel")
|
|
|
|
self.DetectionSet:AddGroup(awacs)
|
|
|
|
local intel = INTEL:New(self.DetectionSet,self.coalition,self.callsigntxt)
|
|
--intel:SetVerbosity(2)
|
|
intel:SetClusterAnalysis(true,self.debug)
|
|
if self.NoHelos then
|
|
intel:SetFilterCategory({Unit.Category.AIRPLANE})
|
|
else
|
|
intel:SetFilterCategory({Unit.Category.AIRPLANE,Unit.Category.HELICOPTER})
|
|
end
|
|
|
|
-- Callbacks
|
|
local function NewCluster(Cluster)
|
|
self:__NewCluster(5,Cluster)
|
|
end
|
|
function intel:OnAfterNewCluster(From,Event,To,Cluster)
|
|
NewCluster(Cluster)
|
|
end
|
|
|
|
local function NewContact(Contact)
|
|
self:__NewContact(5,Contact)
|
|
end
|
|
function intel:OnAfterNewContact(From,Event,To,Contact)
|
|
NewContact(Contact)
|
|
end
|
|
|
|
local function LostContact(Contact)
|
|
self:__LostContact(5,Contact)
|
|
end
|
|
function intel:OnAfterLostContact(From,Event,To,Contact)
|
|
LostContact(Contact)
|
|
end
|
|
|
|
local function LostCluster(Cluster,Mission)
|
|
self:__LostCluster(5,Cluster,Mission)
|
|
end
|
|
function intel:OnAfterLostCluster(From,Event,To,Cluster,Mission)
|
|
LostCluster(Cluster,Mission)
|
|
end
|
|
|
|
intel:__Start(2)
|
|
|
|
self.intel = intel -- Ops.Intelligence#INTEL
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Get blurred size of group or cluster
|
|
-- @param #AWACS self
|
|
-- @param #number size
|
|
-- @return #number adjusted size
|
|
function AWACS:_GetBlurredSize(size)
|
|
self:T(self.lid.."_GetBlurredSize")
|
|
local threatsize = 0
|
|
local blur = self.RadarBlur
|
|
local blurmin = 100 - blur
|
|
local blurmax = 100 + blur
|
|
local actblur = math.random(blurmin,blurmax) / 100
|
|
threatsize = math.floor(size * actblur)
|
|
return threatsize
|
|
end
|
|
|
|
--- [Internal] Get threat level as clear test
|
|
-- @param #AWACS self
|
|
-- @param #number threatlevel
|
|
-- @return #string threattext
|
|
function AWACS:_GetThreatLevelText(threatlevel)
|
|
self:T(self.lid.."_GetThreatLevelText")
|
|
local threattext = "GREEN"
|
|
if threatlevel <= AWACS.THREATLEVEL.GREEN then
|
|
threattext = "GREEN"
|
|
elseif threatlevel <= AWACS.THREATLEVEL.AMBER then
|
|
threattext = "AMBER"
|
|
else
|
|
threattext = "RED"
|
|
end
|
|
return threattext
|
|
end
|
|
|
|
--- [Internal] Get BRA text for TTS
|
|
-- @param #AWACS self
|
|
-- @param Core.Point#COORDINATE clustercoordinate
|
|
-- @return #string BRAText
|
|
function AWACS:_GetBRAfromBullsOrAO(clustercoordinate)
|
|
self:T(self.lid.."__GetBRAfromBullsOrAO")
|
|
local refcoord = self.AOCoordinate -- Core.Point#COORDINATE
|
|
local BRAText = ""
|
|
if not self.UseBullsAO then
|
|
-- get BR from AO
|
|
BRAText = "AO "..refcoord:ToStringBR(clustercoordinate)
|
|
else
|
|
-- get BR from Bulls
|
|
BRAText = clustercoordinate:ToStringBULLS(self.coalition)
|
|
end
|
|
return BRAText
|
|
end
|
|
|
|
--- [Internal] Register Task for Group by GID
|
|
-- @param #AWACS self
|
|
-- @param #number GroupID ManagedGroup ID
|
|
-- @param #AWACS.TaskDescription Description Short Description Task Type
|
|
-- @param #string ScreenText Long task description for screen output
|
|
-- @param #table Object Object for Ops.Target#TARGET assignment
|
|
-- @return #AWACS self
|
|
function AWACS:_CreateTaskForGroup(GroupID,Description,ScreenText,Object)
|
|
self:I(self.lid.."_CreateTaskForGroup "..GroupID .." Description: "..Description)
|
|
local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup
|
|
local task = {} -- #AWACS.ManagedTask
|
|
self.ManagedTaskID = self.ManagedTaskID + 1
|
|
task.TID = self.ManagedTaskID
|
|
task.AssignedGroupID = GroupID
|
|
task.Status = AWACS.TaskStatus.ASSIGNED
|
|
task.ToDo = Description
|
|
task.Target = TARGET:New(Object)
|
|
task.ScreenText = ScreenText
|
|
if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then
|
|
task.Target.Type = TARGET.ObjectType.ZONE
|
|
end
|
|
self.AssignedTasks:Push(task,task.TID)
|
|
--self:I({task})
|
|
managedgroup.HasAssignedTask = true
|
|
managedgroup.CurrentTask = task.TID
|
|
self.ManagedGrps[GroupID] = managedgroup
|
|
--if managedgroup.IsPlayer then
|
|
--self.TaskedCAPHuman:Push(GroupID)
|
|
--elseif managedgroup.IsAI then
|
|
--self.TaskedCAPAI:Push(GroupID)
|
|
--end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Read registered Task for Group by its ID
|
|
-- @param #AWACS self
|
|
-- @param #number GroupID ManagedGroup ID
|
|
-- @return #AWACS.ManagedTask Task or nil if n/e
|
|
function AWACS:_ReadAssignedTaskFromGID(GroupID)
|
|
self:T(self.lid.."_GetAssignedTaskFromGID "..GroupID)
|
|
local managedgroup = self.ManagedGrps[GroupID] -- #AWACS.ManagedGroup
|
|
if managedgroup and managedgroup.HasAssignedTask then
|
|
local TaskID = managedgroup.CurrentTask
|
|
if self.AssignedTasks:HasUniqueID(TaskID) then
|
|
return self.AssignedTasks:ReadByID(TaskID)
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- [Internal] Read assigned Group from a TaskID
|
|
-- @param #AWACS self
|
|
-- @param #number TaskID ManagedTask ID
|
|
-- @return #AWACS.ManagedGroup Group structure or nil if n/e
|
|
function AWACS:_ReadAssignedGroupFromTID(TaskID)
|
|
self:T(self.lid.."_ReadAssignedGroupFromTID "..TaskID)
|
|
if self.AssignedTasks:HasUniqueID(TaskID) then
|
|
local task = self.AssignedTasks:ReadByID(TaskID) -- #AWACS.ManagedTask
|
|
if task and task.AssignedGroupID and task.AssignedGroupID > 0 then
|
|
return self.ManagedGrps[task.AssignedGroupID]
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
--- [Internal] Create new idle task from contact to pick up later
|
|
-- @param #AWACS self
|
|
-- @param #string Description Task Type
|
|
-- @param #table Object Object of TARGET
|
|
-- @param Ops.Intelligence#INTEL.Contact Contact
|
|
-- @return #AWACS self
|
|
function AWACS:_CreateIdleTaskForContact(Description,Object,Contact)
|
|
self:T(self.lid.."_CreateIdleTaskForContact "..Description)
|
|
local task = {} -- #AWACS.ManagedTask
|
|
self.ManagedTaskID = self.ManagedTaskID + 1
|
|
task.TID = self.ManagedTaskID
|
|
task.AssignedGroupID = 0
|
|
task.Status = AWACS.TaskStatus.IDLE
|
|
task.ToDo = Description
|
|
task.Target = TARGET:New(Object)
|
|
task.Contact = Contact
|
|
task.IsContact = true
|
|
task.ScreenText = Description
|
|
if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then
|
|
task.Target.Type = TARGET.ObjectType.ZONE
|
|
end
|
|
self.OpenTasks:Push(task,task.TID)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Create new idle task from cluster to pick up later
|
|
-- @param #AWACS self
|
|
-- @param #string Description Task Type
|
|
-- @param #table Object Object of TARGET
|
|
-- @param Ops.Intelligence#INTEL.Cluster Cluster
|
|
-- @return #AWACS self
|
|
function AWACS:_CreateIdleTaskForCluster(Description,Object,Cluster)
|
|
self:T(self.lid.."_CreateIdleTaskForCluster "..Description)
|
|
local task = {} -- #AWACS.ManagedTask
|
|
self.ManagedTaskID = self.ManagedTaskID + 1
|
|
task.TID = self.ManagedTaskID
|
|
task.AssignedGroupID = 0
|
|
task.Status = AWACS.TaskStatus.IDLE
|
|
task.ToDo = Description
|
|
--self:T({Cluster.Contacts})
|
|
--task.Target = TARGET:New(Cluster.Contacts[1])
|
|
task.Target = TARGET:New(self.intel:GetClusterCoordinate(Cluster))
|
|
task.Cluster = Cluster
|
|
task.IsCluster = true
|
|
task.ScreenText = Description
|
|
if Description == AWACS.TaskDescription.ANCHOR or Description == AWACS.TaskDescription.REANCHOR then
|
|
task.Target.Type = TARGET.ObjectType.ZONE
|
|
end
|
|
self.OpenTasks:Push(task,task.TID)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Create radio entry to tell players that CAP is on station in Anchor
|
|
-- @param #AWACS self
|
|
-- @param #number GID Group ID
|
|
-- @return #AWACS self
|
|
function AWACS:_MessageAIReadyForTasking(GID)
|
|
self:I(self.lid.."_MessageAIReadyForTasking")
|
|
-- obtain group details
|
|
if GID >0 and self.ManagedGrps[GID] then
|
|
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
local GFCallsign = self:_GetCallSign(managedgroup.Group)
|
|
local TextTTS = string.format("%s. %s. On station over anchor %d at angels %d. Ready for tasking.",GFCallsign,self.callsigntxt,managedgroup.AnchorStackNo or 1,managedgroup.AnchorStackAngels or 25)
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.TextTTS = TextTTS
|
|
RadioEntry.TextScreen = ""
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.IsGroup = false
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16
|
|
RadioEntry.ToScreen = false
|
|
RadioEntry.FromAI = true
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Check available tasks and status
|
|
-- @param #AWACS self
|
|
-- @return #AWACS self
|
|
function AWACS:_CheckTaskQueue()
|
|
self:I(self.lid.."_CheckTaskQueue")
|
|
local opentasks = 0
|
|
local assignedtasks = 0
|
|
if self.OpenTasks:IsNotEmpty() then
|
|
opentasks = self.OpenTasks:GetSize()
|
|
self:I("Open Tasks: " .. opentasks)
|
|
local taskstack = self.OpenTasks:GetPointerStack()
|
|
for _id,_entry in pairs(taskstack) do
|
|
local data = _entry -- Utilities.FiFo#FIFO.IDEntry
|
|
local entry = data.data -- #AWACS.ManagedTask
|
|
local target = entry.Target -- Ops.Target#TARGET
|
|
local description = entry.ToDo
|
|
self:I("ToDo = "..description)
|
|
if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then
|
|
self:I("Open Tasks ANCHOR/REANCHOR")
|
|
-- see if we have reached the anchor zone
|
|
local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup
|
|
if managedgroup then
|
|
local group = managedgroup.Group
|
|
local groupcoord = group:GetCoordinate()
|
|
local zone = target:GetObject() -- Core.Zone#ZONE
|
|
self:T({zone})
|
|
if group:IsInZone(zone) then
|
|
self:I("Open Tasks ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID)
|
|
-- made it
|
|
target:Stop()
|
|
-- pull task from OpenTasks
|
|
self.OpenTasks:PullByPointer(_id)
|
|
--[[ add group to idle stack
|
|
if managedgroup.IsAI then
|
|
self.TaskedCAPAI:PullByPointer(entry.AssignedGroupID)
|
|
self.CAPIdleAI:Push(entry.AssignedGroupID)
|
|
elseif managedgroup.IsPlayer then
|
|
self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID)
|
|
self.CAPIdleHuman:Push(entry.AssignedGroupID)
|
|
end
|
|
--]]
|
|
else
|
|
-- not there yet
|
|
end
|
|
end
|
|
elseif description == AWACS.TaskDescription.INTERCEPT then
|
|
-- TODO
|
|
elseif description == AWACS.TaskDescription.RTB then
|
|
-- TODO
|
|
end
|
|
end
|
|
end
|
|
|
|
if self.AssignedTasks:IsNotEmpty() then
|
|
opentasks = self.AssignedTasks:GetSize()
|
|
self:I("Assigned Tasks: " .. opentasks)
|
|
local taskstack = self.AssignedTasks:GetPointerStack()
|
|
for _id,_entry in pairs(taskstack) do
|
|
local data = _entry -- Utilities.FiFo#FIFO.IDEntry
|
|
local entry = data.data -- #AWACS.ManagedTask
|
|
local target = entry.Target -- Ops.Target#TARGET
|
|
local description = entry.ToDo
|
|
self:I("ToDo = "..description)
|
|
if description == AWACS.TaskDescription.ANCHOR or description == AWACS.TaskDescription.REANCHOR then
|
|
self:I("Open Tasks ANCHOR/REANCHOR")
|
|
-- see if we have reached the anchor zone
|
|
local managedgroup = self.ManagedGrps[entry.AssignedGroupID] -- #AWACS.ManagedGroup
|
|
if managedgroup then
|
|
local group = managedgroup.Group
|
|
local groupcoord = group:GetCoordinate()
|
|
local zone = target:GetObject() -- Core.Zone#ZONE
|
|
self:T({zone})
|
|
if group:IsInZone(zone) then
|
|
self:I("Open Tasks ANCHOR/REANCHOR success for GroupID "..entry.AssignedGroupID)
|
|
-- made it
|
|
target:Stop()
|
|
-- pull task from OpenTasks
|
|
self.AssignedTasks:PullByPointer(_id)
|
|
-- add group to idle stack
|
|
if managedgroup.IsAI then
|
|
-- message AI on station
|
|
self:_MessageAIReadyForTasking(managedgroup.GID)
|
|
elseif managedgroup.IsPlayer then
|
|
--self.TaskedCAPHuman:PullByPointer(entry.AssignedGroupID)
|
|
---self.CAPIdleHuman:Push(entry.AssignedGroupID)
|
|
end
|
|
else
|
|
-- not there yet
|
|
end
|
|
end
|
|
elseif description == AWACS.TaskDescription.INTERCEPT then
|
|
-- TODO
|
|
elseif description == AWACS.TaskDescription.RTB then
|
|
-- TODO
|
|
end
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Write stats to log
|
|
-- @param #AWACS self
|
|
-- @return #AWACS self
|
|
function AWACS:_LogStatistics()
|
|
self:T(self.lid.."_LogStatistics")
|
|
return self
|
|
end
|
|
|
|
--- [User] Add another AirWing for CAP Flights under management
|
|
-- @param #AWACS self
|
|
-- @param Ops.AirWing#AIRWING AirWing The AirWing to (also) obtain CAP flights from
|
|
-- @return #AWACS self
|
|
function AWACS:AddCAPAirWing(AirWing)
|
|
self:I(self.lid.."AddCAPAirWing")
|
|
if AirWing then
|
|
-- TODO - Test Install callback
|
|
-- TODO - add distance to AO as UniqueID
|
|
AirWing:SetUsingOpsAwacs(self)
|
|
self.CAPAirwings:Push(AirWing)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- Recruit assets for a given TARGET.
|
|
-- @param #AWACS self
|
|
-- @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 AWACS:RecruitAssets(MissionType, NassetsMin, NassetsMax)
|
|
|
|
-- Cohorts.
|
|
local Cohorts={}
|
|
local AWFiFo = self.CAPAirwings -- Utilities.FiFo#FIFO
|
|
local AWStack = AWFiFo:GetPointerStack()
|
|
local AirWingList = {}
|
|
|
|
for _ID,_AWID in pairs(AWStack) do
|
|
local SubAW = self.CAPAirwings:ReadByPointer(_ID)
|
|
if SubAW then
|
|
table.insert(AirWingList,SubAW)
|
|
end
|
|
end
|
|
|
|
for _,_legion in pairs(AirWingList) 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
|
|
|
|
-- Target position.
|
|
local TargetVec2=self.OpsZone:GetVec2()
|
|
|
|
-- Recruit assets.
|
|
local recruited, assets, legions=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2)
|
|
|
|
return recruited, assets, legions
|
|
end
|
|
|
|
--- [Internal] Announce a new contact
|
|
-- @param #AWACS self
|
|
-- @param Ops.Intelligence#INTEL.Contact Contact
|
|
-- @param #boolean IsNew
|
|
-- @param Wrapper.Group#GROUP Group Announce to Group if not nil
|
|
-- @param #boolean IsBogeyDope If true, this is a bogey dope announcement
|
|
-- @return #AWACS self
|
|
function AWACS:_AnnounceContact(Contact,IsNew,Group,IsBogeyDope)
|
|
self:T(self.lid.."_AnnounceContact")
|
|
-- do we have a group to talk to?
|
|
local isGroup = false
|
|
local GID = 0
|
|
local grpcallsign = "Unknown 1 1"
|
|
if Group and Group:IsAlive() then
|
|
GID, isGroup = self:_GetManagedGrpID(Group)
|
|
self:T("GID="..GID.." CheckedIn = "..tostring(isGroup))
|
|
grpcallsign = self:_GetCallSign(Group) or "Unknown 1 1"
|
|
end
|
|
local contact = Contact -- Ops.Intelligence#INTEL.Contact
|
|
local intel = self.intel -- Ops.Intelligence#INTEL
|
|
local size = contact.group:CountAliveUnits()
|
|
local threatsize = self:_GetBlurredSize(size)
|
|
local threatlevel = contact.threatlevel
|
|
local threattext = self:_GetThreatLevelText(threatlevel)
|
|
local clustercoordinate = contact.position
|
|
|
|
local BRAfromBulls = self:_GetBRAfromBullsOrAO(clustercoordinate)
|
|
if isGroup then
|
|
BRAfromBulls = clustercoordinate:ToStringBRA(Group:GetCoordinate())
|
|
end
|
|
|
|
local Warnlevel = "Early Warning."
|
|
|
|
if self.OpsZone:IsVec2InZone(clustercoordinate:GetVec2()) and not IsBogeyDope then
|
|
Warnlevel = "Warning."
|
|
elseif IsBogeyDope then
|
|
Warnlevel = ""
|
|
end
|
|
|
|
if IsNew then
|
|
Warnlevel = Warnlevel .. " New"
|
|
end
|
|
|
|
Warnlevel = string.format("%s %s", Warnlevel, threattext)
|
|
|
|
if isGroup then
|
|
Warnlevel = string.format("%s. %s",grpcallsign,Warnlevel)
|
|
end
|
|
|
|
-- TTS
|
|
local TextTTS = string.format("%s. %s %d ship contact. %s",self.callsigntxt,Warnlevel,threatsize,BRAfromBulls)
|
|
|
|
-- TextOutput
|
|
local TextScreen = string.format("%s. %s %d ship contact.\n%s\nThreatlevel %s",self.callsigntxt,Warnlevel,threatsize,BRAfromBulls,threattext)
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.TextTTS = TextTTS
|
|
RadioEntry.TextScreen = TextScreen
|
|
RadioEntry.IsNew = IsNew
|
|
RadioEntry.IsGroup = isGroup
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.Duration = STTS.getSpeechTime(TextTTS,1.2,false)+2 or 16
|
|
RadioEntry.ToScreen = true
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Check for alive OpsGroup from Mission OpsGroups table
|
|
-- @param #AWACS self
|
|
-- @param #table OpsGroups
|
|
-- @return Ops.OpsGroup#OPSGROUP or nil
|
|
function AWACS:_GetAliveOpsGroupFromTable(OpsGroups)
|
|
self:T(self.lid.."_GetAliveOpsGroupFromTable")
|
|
local handback = nil
|
|
for _,_OG in pairs(OpsGroups or {}) do
|
|
local OG = _OG -- Ops.OpsGroup#OPSGROUP
|
|
if OG and OG:IsFlightgroup() and OG:IsAlive() then
|
|
handback = OG
|
|
self:T("Handing back OG: " .. OG:GetName())
|
|
break
|
|
end
|
|
end
|
|
return handback
|
|
end
|
|
|
|
--- [Internal] Check Enough AI CAP on Station
|
|
-- @param #AWACS self
|
|
-- @return #AWACS self
|
|
function AWACS:_CheckAICAPOnStation()
|
|
self:I(self.lid.."_CheckAICAPOnStation")
|
|
if self.MaxAIonCAP > 0 then
|
|
local onstation = self.AICAPMissions:Count()
|
|
-- control number of AI CAP Flights
|
|
if onstation < self.MaxAIonCAP then
|
|
-- not enough
|
|
local AnchorStackNo,free = self:_GetFreeAnchorStack()
|
|
if free then
|
|
-- recruit assets (thanks to FF!)
|
|
local recruited, assets, airwings = self:RecruitAssets(AUFTRAG.Type.CAP,1,1)
|
|
if recruited then
|
|
-- yes, there should be ONE asset and ONE AW
|
|
|
|
self:I(self.lid..string.format("Recruited %d assets for mission type CAP", #assets))
|
|
local mission = AUFTRAG:NewCAP(self.AnchorZone,20000,350,self.AnchorZone:GetCoordinate(),nil,nil,{})
|
|
|
|
-- Add asset to mission.
|
|
if mission then
|
|
for _,_asset in pairs(assets) do
|
|
local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem
|
|
mission:AddAsset(asset)
|
|
asset.legion:AddMission(mission)
|
|
-- Debug info.
|
|
self:I(self.lid..string.format("Assigning mission \"%s\" [%s] to AW \"%s\"", mission.name, mission.type, asset.legion.alias))
|
|
end
|
|
end
|
|
|
|
-- TODO - necessary?
|
|
LEGION.UnRecruitAssets(assets)
|
|
|
|
--self.AirWing:AddMission(mission)
|
|
self.AICAPMissions:Push(mission,mission.auftragsnummer)
|
|
|
|
else
|
|
-- no
|
|
self:I(self.lid.."Could NOT recruit assets for mission type CAP")
|
|
end
|
|
end
|
|
elseif onstation > self.MaxAIonCAP then
|
|
-- too many, send one home
|
|
local mission = self.AICAPMissions:Pull() -- Ops.Auftrag#AUFTRAG
|
|
local Groups = mission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(Groups)
|
|
mission:__Cancel(5)
|
|
self:_CheckOut(OpsGroup)
|
|
end
|
|
-- Check CAP Mission states
|
|
if onstation > 0 then
|
|
local missionIDs = self.AICAPMissions:GetIDStackSorted()
|
|
--self:_CheckInAI(FlightGroup,FlightGroup:GetGroup(),Mission.auftragsnummer)
|
|
-- get mission type and state
|
|
for _,_MissionID in pairs(missionIDs) do
|
|
local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG
|
|
self:T("Looking at AuftragsNr " .. mission.auftragsnummer)
|
|
local type = mission:GetType()
|
|
local state = mission:GetState()
|
|
if type == AUFTRAG.Type.CAP then
|
|
local OpsGroups = mission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups)
|
|
local FGstate = mission:GetGroupStatus(OpsGroup)
|
|
-- FG ready?
|
|
if FGstate == AUFTRAG.Status.STARTED or FGstate == AUFTRAG.Status.EXECUTING then
|
|
-- has this group checked in already? Avoid double tasking
|
|
local GID, CheckedInAlready = self:_GetManagedGrpID(OpsGroup:GetGroup())
|
|
if not CheckedInAlready then
|
|
self:_SetAIROE(OpsGroup,OpsGroup:GetGroup())
|
|
self:_CheckInAI(OpsGroup,OpsGroup:GetGroup(),mission.auftragsnummer)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- cycle mission status
|
|
if onstation > 0 then
|
|
local report = REPORT:New("CAP Mission Status")
|
|
report:Add("===============")
|
|
local missionIDs = self.AICAPMissions:GetIDStackSorted()
|
|
local i = 1
|
|
for _,_MissionID in pairs(missionIDs) do
|
|
--for i=1,self.MaxAIonCAP do
|
|
local mission = self.AICAPMissions:ReadByID(_MissionID) -- Ops.Auftrag#AUFTRAG
|
|
--local mission = self.AICAPMissions:ReadByPointer(i) -- Ops.Auftrag#AUFTRAG
|
|
if mission then
|
|
i = i + 1
|
|
report:Add(string.format("Entry %d",i))
|
|
report:Add(string.format("Mission No %d",mission.auftragsnummer))
|
|
report:Add(string.format("Mission Type %s",mission:GetType()))
|
|
report:Add(string.format("Mission State %s",mission:GetState()))
|
|
local OpsGroups = mission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
|
if OpsGroup then
|
|
local OpsName = OpsGroup:GetName() or "Unknown"
|
|
local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown"
|
|
report:Add(string.format("Mission FG %s",OpsName))
|
|
report:Add(string.format("Callsign %s",OpsCallSign))
|
|
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
|
|
else
|
|
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
|
end
|
|
report:Add(string.format("Target Type %s",mission:GetTargetType()))
|
|
end
|
|
report:Add("===============")
|
|
end
|
|
if self.debug then
|
|
self:T(report:Text())
|
|
end
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Set ROE for AI CAP
|
|
-- @param #AWACS self
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup
|
|
-- @param Wrapper.Group#GROUP Group
|
|
-- @return #AWACS self
|
|
function AWACS:_SetAIROE(FlightGroup,Group)
|
|
self:T(self.lid.."_SetAIROE")
|
|
local ROE = self.AwacsROE or AWACS.ROE.POLICE
|
|
local ROT = self.AwacsROT or AWACS.ROT.PASSIVE
|
|
|
|
-- TODO adjust to AWACS set ROE
|
|
-- for the time being set to be defensive
|
|
Group:OptionAlarmStateGreen()
|
|
Group:OptionECM_OnlyLockByRadar()
|
|
Group:OptionROEHoldFire()
|
|
Group:OptionROTEvadeFire()
|
|
Group:OptionRTBBingoFuel(true)
|
|
Group:OptionKeepWeaponsOnThreat()
|
|
local callname = self.AICAPCAllName or CALLSIGN.F16.Viper
|
|
self.AICAPCAllNumber = self.AICAPCAllNumber + 1
|
|
Group:CommandSetCallsign(callname,math.fmod(self.AICAPCAllNumber,9))
|
|
-- FG level
|
|
FlightGroup:SetDefaultAlarmstate(AI.Option.Ground.val.ALARM_STATE.GREEN)
|
|
FlightGroup:SetDefaultROE(ENUMS.ROE.WeaponHold)
|
|
FlightGroup:SetDefaultROT(ENUMS.ROT.EvadeFire)
|
|
FlightGroup:SetFuelLowRTB(true)
|
|
FlightGroup:SetFuelLowThreshold(0.2)
|
|
FlightGroup:SetEngageDetectedOff()
|
|
FlightGroup:SetOutOfAAMRTB(true)
|
|
return self
|
|
end
|
|
|
|
-- TODO FSMs
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- FSM Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- [Internal] onafterStart
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AWACS self
|
|
function AWACS:onafterStart(From, Event, To)
|
|
self:I({From, Event, To})
|
|
|
|
-- Set up control zone
|
|
self.ControlZone = ZONE_RADIUS:New(self.OpsZone:GetName(),self.OpsZone:GetVec2(),UTILS.NMToMeters(self.ControlZoneRadius))
|
|
if self.debug then
|
|
self.ControlZone:DrawZone(-1,{0,1,0},1,{1,0,0},0.05,3,true)
|
|
MARKER:New(self.ControlZone:GetCoordinate(),"Control Zone"):ToAll()
|
|
end
|
|
|
|
-- set up the AWACS and let it orbit
|
|
local AwacsAW = self.AirWing -- Ops.AirWing#AIRWING
|
|
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
|
|
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
|
|
mission:SetTime(nil,timeonstation)
|
|
|
|
AwacsAW:AddMission(mission)
|
|
|
|
--[[ callback functions
|
|
local function StartSettings(FlightGroup,Mission)
|
|
self:_StartSettings(FlightGroup,Mission)
|
|
end
|
|
|
|
function AwacsAW:OnAfterFlightOnMission(From,Event,To,FlightGroup,Mission)
|
|
StartSettings(FlightGroup,Mission)
|
|
end
|
|
--]]
|
|
|
|
self.AwacsMission = mission
|
|
self.AwacsInZone = false -- not yet arrived or gone again
|
|
|
|
self:__Status(-30)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterStatus
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AWACS self
|
|
function AWACS:onafterStatus(From, Event, To)
|
|
self:I({From, Event, To})
|
|
|
|
self:_SetClientMenus()
|
|
|
|
local awacs = nil
|
|
if self.AwacsFG then
|
|
awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP
|
|
end
|
|
if awacs and awacs:IsAlive() and not self.AwacsInZone then
|
|
-- check if we arrived
|
|
local orbitzone = self.OrbitZone -- Core.Zone#ZONE
|
|
if awacs:IsInZone(orbitzone) then
|
|
-- arrived
|
|
self.AwacsInZone = true
|
|
self:I(self.lid.."Arrived in Orbit Zone: " .. orbitzone:GetName())
|
|
local text = string.format("%s on station for A O %s control.",self.callsigntxt,self.OpsZone:GetName() or "A O")
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = text
|
|
RadioEntry.TextScreen = text
|
|
RadioEntry.ToScreen = true
|
|
RadioEntry.Duration = STTS.getSpeechTime(text,1.1,false) or 8
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
self:_StartIntel(awacs)
|
|
end
|
|
end
|
|
|
|
--------------------------------
|
|
-- AWACS
|
|
--------------------------------
|
|
|
|
if (awacs and awacs:IsAlive()) then
|
|
|
|
-- Check on Awacs Mission Status
|
|
local AWmission = self.AwacsMission -- Ops.Auftrag#AUFTRAG
|
|
local awstatus = AWmission:GetState()
|
|
local AWmissiontime = (timer.getTime() - self.AwacsTimeStamp)
|
|
|
|
local AWTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - AWmissiontime),0) -- seconds
|
|
|
|
AWTOSLeft = UTILS.Round(AWTOSLeft/60,0) -- minutes
|
|
|
|
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0)
|
|
|
|
local Changedue = "No"
|
|
|
|
if not self.ShiftChangeAwacsFlag and (AWTOSLeft <= ChangeTime or AWmission:IsOver()) then
|
|
Changedue = "Yes"
|
|
self.ShiftChangeAwacsFlag = true
|
|
self:__AwacsShiftChange(2)
|
|
end
|
|
|
|
local report = REPORT:New("AWACS:")
|
|
report:Add("====================")
|
|
report:Add("AWACS:")
|
|
report:Add(string.format("Auftrag Status: %s",awstatus))
|
|
report:Add(string.format("TOS Left: %d min",AWTOSLeft))
|
|
report:Add(string.format("Needs ShiftChange: %s",Changedue))
|
|
|
|
local OpsGroups = AWmission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
|
if OpsGroup then
|
|
local OpsName = OpsGroup:GetName() or "Unknown"
|
|
local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown"
|
|
report:Add(string.format("Mission FG %s",OpsName))
|
|
report:Add(string.format("Callsign %s",OpsCallSign))
|
|
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
|
|
else
|
|
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
|
end
|
|
|
|
-- Check for replacement mission - if any
|
|
if self.ShiftChangeAwacsFlag and self.ShiftChangeAwacsRequested then -- Ops.Auftrag#AUFTRAG
|
|
AWmission = self.EscortMissionReplacement
|
|
local esstatus = AWmission:GetState()
|
|
local ESmissiontime = (timer.getTime() - self.AwacsTimeStamp)
|
|
local ESTOSLeft = UTILS.Round((((self.AwacsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds
|
|
ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes
|
|
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0)
|
|
--local Changedue = "No"
|
|
|
|
--report:Add("====================")
|
|
report:Add("AWACS REPLACEMENT:")
|
|
report:Add(string.format("Auftrag Status: %s",esstatus))
|
|
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
|
|
--report:Add(string.format("Needs ShiftChange: %s",Changedue))
|
|
|
|
local OpsGroups = AWmission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
|
if OpsGroup then
|
|
local OpsName = OpsGroup:GetName() or "Unknown"
|
|
local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown"
|
|
report:Add(string.format("Mission FG %s",OpsName))
|
|
report:Add(string.format("Callsign %s",OpsCallSign))
|
|
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
|
|
else
|
|
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
|
end
|
|
|
|
if AWmission:IsExecuting() then
|
|
-- make the actual change in the queue
|
|
self.ShiftChangeAwacsFlag = false
|
|
self.ShiftChangeAwacsRequested = false
|
|
-- cancel old mission
|
|
if self.AwacsMission and self.AwacsMission:IsNotOver() then
|
|
self.AwacsMission:Cancel()
|
|
end
|
|
self.AwacsMission = self.AwacsMissionReplacement
|
|
self.AwacsMissionReplacement = nil
|
|
self.AwacsTimeStamp = timer.getTime()
|
|
report:Add("*** Replacement DONE ***")
|
|
end
|
|
report:Add("====================")
|
|
end
|
|
|
|
--------------------------------
|
|
-- ESCORTS
|
|
--------------------------------
|
|
|
|
if self.HasEscorts then
|
|
local ESmission = self.EscortMission -- Ops.Auftrag#AUFTRAG
|
|
local esstatus = ESmission:GetState()
|
|
local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp)
|
|
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds
|
|
ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes
|
|
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0)
|
|
local Changedue = "No"
|
|
|
|
if (ESTOSLeft <= ChangeTime and not self.ShiftChangeEscortsFlag) or (ESmission:IsOver() and not self.ShiftChangeEscortsFlag) then
|
|
Changedue = "Yes"
|
|
self.ShiftChangeEscortsFlag = true -- set this back when new Escorts arrived
|
|
self:__EscortShiftChange(2)
|
|
end
|
|
|
|
report:Add("====================")
|
|
report:Add("ESCORTS:")
|
|
report:Add(string.format("Auftrag Status: %s",esstatus))
|
|
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
|
|
report:Add(string.format("Needs ShiftChange: %s",Changedue))
|
|
|
|
local OpsGroups = ESmission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
|
if OpsGroup then
|
|
local OpsName = OpsGroup:GetName() or "Unknown"
|
|
local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown"
|
|
report:Add(string.format("Mission FG %s",OpsName))
|
|
report:Add(string.format("Callsign %s",OpsCallSign))
|
|
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
|
|
else
|
|
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
|
end
|
|
|
|
report:Add("====================")
|
|
|
|
-- Check for replacement mission - if any
|
|
if self.ShiftChangeEscortsFlag and self.ShiftChangeEscortsRequested then -- Ops.Auftrag#AUFTRAG
|
|
ESmission = self.EscortMissionReplacement
|
|
local esstatus = ESmission:GetState()
|
|
local ESmissiontime = (timer.getTime() - self.EscortsTimeStamp)
|
|
local ESTOSLeft = UTILS.Round((((self.EscortsTimeOnStation+self.ShiftChangeTime)*3600) - ESmissiontime),0) -- seconds
|
|
ESTOSLeft = UTILS.Round(ESTOSLeft/60,0) -- minutes
|
|
local ChangeTime = UTILS.Round(((self.ShiftChangeTime * 3600)/60),0)
|
|
--local Changedue = "No"
|
|
|
|
--report:Add("====================")
|
|
report:Add("ESCORTS REPLACEMENT:")
|
|
report:Add(string.format("Auftrag Status: %s",esstatus))
|
|
report:Add(string.format("TOS Left: %d min",ESTOSLeft))
|
|
--report:Add(string.format("Needs ShiftChange: %s",Changedue))
|
|
|
|
local OpsGroups = ESmission:GetOpsGroups()
|
|
local OpsGroup = self:_GetAliveOpsGroupFromTable(OpsGroups) -- Ops.OpsGroup#OPSGROUP
|
|
if OpsGroup then
|
|
local OpsName = OpsGroup:GetName() or "Unknown"
|
|
local OpsCallSign = OpsGroup:GetCallsignName() or "Unknown"
|
|
report:Add(string.format("Mission FG %s",OpsName))
|
|
report:Add(string.format("Callsign %s",OpsCallSign))
|
|
report:Add(string.format("Mission FG State %s",OpsGroup:GetState()))
|
|
else
|
|
report:Add("***** Cannot obtain (yet) this missions OpsGroup!")
|
|
end
|
|
|
|
if ESmission:IsExecuting() then
|
|
-- make the actual change in the queue
|
|
self.ShiftChangeEscortsFlag = false
|
|
self.ShiftChangeEscortsRequested = false
|
|
-- cancel old mission
|
|
if self.EscortMission and self.EscortMission:IsNotOver() then
|
|
self.EscortMission:Cancel()
|
|
end
|
|
self.EscortMission = self.EscortMissionReplacement
|
|
self.EscortMissionReplacement = nil
|
|
self.EscortsTimeStamp = timer.getTime()
|
|
report:Add("*** Replacement DONE ***")
|
|
end
|
|
report:Add("====================")
|
|
end
|
|
end
|
|
|
|
if self.debug then
|
|
self:I(report:Text())
|
|
end
|
|
-- Check on AUFTRAG status for CAP AI
|
|
self:_CheckAICAPOnStation()
|
|
else
|
|
-- do other stuff
|
|
|
|
end
|
|
|
|
-- Check task queue (both)
|
|
self:_CheckTaskQueue()
|
|
|
|
self:__Status(30)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterStop
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AWACS self
|
|
function AWACS:onafterStop(From, Event, To)
|
|
self:T({From, Event, To})
|
|
-- unhandle stuff, exit intel
|
|
|
|
self.intel:Stop()
|
|
|
|
local AWFiFo = self.CAPAirwings -- Utilities.FiFo#FIFO
|
|
local AWStack = AWFiFo:GetPointerStack()
|
|
for _ID,_AWID in pairs(AWStack) do
|
|
local SubAW = self.CAPAirwings:ReadByPointer(_ID)
|
|
if SubAW then
|
|
SubAW:RemoveUsingOpsAwacs()
|
|
end
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterAssignAnchor
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param #number GID Group ID
|
|
-- @return #AWACS self
|
|
function AWACS:onafterAssignAnchor(From, Event, To, GID)
|
|
self:T({From, Event, To, "GID = " .. GID})
|
|
self:_AssignAnchorToID(GID)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterCheckedOut
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param #AWACS.ManagedGroup.GID Group ID
|
|
-- @param #number AnchorStackNo
|
|
-- @param #number Angels
|
|
-- @return #AWACS self
|
|
function AWACS:onafterCheckedOut(From, Event, To, GID, AnchorStackNo, Angels)
|
|
self:T({From, Event, To, "GID = " .. GID})
|
|
self:_RemoveIDFromAnchor(GID,AnchorStackNo,Angels)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterAssignedAnchor
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param #number GID Managed Group ID
|
|
-- @param #AWACS.AnchorData Anchor
|
|
-- @param #number AnchorStackNo
|
|
-- @return #AWACS self
|
|
function AWACS:onafterAssignedAnchor(From, Event, To, GID, Anchor, AnchorStackNo, AnchorAngels)
|
|
self:I({From, Event, To, "GID=" .. GID, "Stack=" .. AnchorStackNo})
|
|
-- TODO
|
|
local managedgroup = self.ManagedGrps[GID] -- #AWACS.ManagedGroup
|
|
managedgroup.AnchorStackNo = AnchorStackNo
|
|
managedgroup.AnchorStackAngels = AnchorAngels
|
|
self.ManagedGrps[GID] = managedgroup
|
|
local isPlayer = managedgroup.IsPlayer
|
|
local isAI = managedgroup.IsAI
|
|
local Group = managedgroup.Group
|
|
local CallSign = managedgroup.CallSign or "unknown 1 1"
|
|
local AnchorName = Anchor.AnchorZone:GetName() or "unknown"
|
|
local AnchorCoordTxt = Anchor.AnchorZoneCoordinateText or "unknown"
|
|
local Angels = AnchorAngels or 25
|
|
local AnchorSpeed = self.CapSpeedBase or 220
|
|
local AuftragsNr = managedgroup.CurrentTask
|
|
|
|
local textTTS = string.format("%s. %s. Anchor at %s at angels %d doing %d knots. Wait for task assignment.",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed)
|
|
local ROEROT = self.AwacsROE.." "..self.AwacsROT
|
|
local textScreen = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s\nWait for task assignment.",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT)
|
|
local TextTasking = string.format("%s. %s.\nAnchor at %s\nAngels %d\nSpeed %d knots\nCoord %s\nROE %s",self.callsigntxt,CallSign,AnchorName,Angels,AnchorSpeed,AnchorCoordTxt,ROEROT)
|
|
|
|
local RadioEntry = {} -- #AWACS.RadioEntry
|
|
RadioEntry.IsNew = true
|
|
RadioEntry.TextTTS = textTTS
|
|
RadioEntry.TextScreen = textScreen
|
|
RadioEntry.GroupID = GID
|
|
RadioEntry.IsGroup = isPlayer
|
|
RadioEntry.Duration = STTS.getSpeechTime(textTTS,1.0,false) or 10
|
|
RadioEntry.ToScreen = isPlayer
|
|
|
|
self.RadioQueue:Push(RadioEntry)
|
|
|
|
self:_CreateTaskForGroup(GID,AWACS.TaskDescription.ANCHOR,TextTasking,Anchor.AnchorZone)
|
|
|
|
if isAI and AuftragsNr and AuftragsNr > 0 and self.AICAPMissions:HasUniqueID(AuftragsNr) then
|
|
-- Change current Auftrag to Orbit at Anchor
|
|
local capalt = Angels*1000
|
|
local capspeed = UTILS.KnotsToAltKIAS(self.CapSpeedBase,capalt)
|
|
local AnchorMission = AUFTRAG:NewORBIT(Anchor.AnchorZoneCoordinate,capalt,capspeed,0,15)
|
|
managedgroup.FlightGroup:AddMission(AnchorMission)
|
|
managedgroup.FlightGroup:GetMissionCurrent():__Cancel(5)
|
|
self.AICAPMissions:PullByID(AuftragsNr)
|
|
self.AICAPMissions:Push(AnchorMission,AnchorMission.auftragsnummer)
|
|
managedgroup.CurrentTask = AnchorMission.auftragsnummer
|
|
self.ManagedGrps[GID] = managedgroup
|
|
end
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterNewCluster
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Ops.Intelligence#INTEL.Cluster Cluster
|
|
-- @return #AWACS self
|
|
function AWACS:onafterNewCluster(From,Event,To,Cluster)
|
|
self:T({From, Event, To, Cluster})
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterNewContact
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Ops.Intelligence#INTEL.Contact Contact
|
|
-- @return #AWACS self
|
|
function AWACS:onafterNewContact(From,Event,To,Contact)
|
|
self:T({From, Event, To, Contact})
|
|
self.Contacts:Push(Contact)
|
|
self:_AnnounceContact(Contact,true,nil,false)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterLostContact
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Ops.Intelligence#INTEL.Contact Contact
|
|
-- @return #AWACS self
|
|
function AWACS:onafterLostContact(From,Event,To,Contact)
|
|
self:T({From, Event, To, Contact})
|
|
-- TODO Check Idle, Assigned Tasks for status
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterLostCluster
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Ops.Intelligence#INTEL.Cluster Cluster
|
|
-- @param Ops.Auftrag#AUFTRAG Mission
|
|
-- @return #AWACS self
|
|
function AWACS:onafterLostCluster(From,Event,To,Cluster,Mission)
|
|
self:T({From, Event, To})
|
|
-- TODO Remove Cluster from Picture
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterCheckRadioQueue
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AWACS self
|
|
function AWACS:onafterCheckRadioQueue(From,Event,To)
|
|
self:T({From, Event, To})
|
|
-- do we have messages queued?
|
|
|
|
local nextcall = 10
|
|
if self.RadioQueue:IsNotEmpty() then
|
|
local RadioEntry = self.RadioQueue:Pull() -- #AWACS.RadioEntry
|
|
|
|
self:T({RadioEntry})
|
|
|
|
if not RadioEntry.FromAI then
|
|
-- AI AWACS Speaking
|
|
self.AwacsFG:RadioTransmission(RadioEntry.TextTTS,1,false)
|
|
else
|
|
-- CAP AI speaking
|
|
if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then
|
|
local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup
|
|
if managedgroup and managedgroup.FlightGroup and managedgroup.FlightGroup:IsAlive() then
|
|
managedgroup.FlightGroup:RadioTransmission(RadioEntry.TextTTS,1,false)
|
|
end
|
|
end
|
|
end
|
|
|
|
if RadioEntry.Duration then nextcall = RadioEntry.Duration end
|
|
if RadioEntry.ToScreen and RadioEntry.TextScreen then
|
|
if RadioEntry.GroupID and RadioEntry.GroupID ~= 0 then
|
|
local managedgroup = self.ManagedGrps[RadioEntry.GroupID] -- #AWACS.ManagedGroup
|
|
if managedgroup and managedgroup.Group and managedgroup.Group:IsAlive() then
|
|
MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToGroup(managedgroup.Group)
|
|
end
|
|
else
|
|
MESSAGE:New(RadioEntry.TextScreen,20,"AWACS"):ToCoalition(self.coalition)
|
|
end
|
|
end
|
|
end
|
|
|
|
if self:Is("Running") then
|
|
-- exit if stopped
|
|
self:__CheckRadioQueue(nextcall+2)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterEscortShiftChange
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AWACS self
|
|
function AWACS:onafterEscortShiftChange(From,Event,To)
|
|
self:I({From, Event, To})
|
|
-- request new Escorts, check if AWACS-FG still alive first!
|
|
if self.AwacsFG and self.ShiftChangeEscortsFlag and not self.ShiftChangeEscortsRequested then
|
|
local awacs = self.AwacsFG:GetGroup() -- Wrapper.Group#GROUP
|
|
if awacs and awacs:IsAlive() then
|
|
-- ok we're good to re-request
|
|
self.ShiftChangeEscortsRequested = true
|
|
self.EscortsTimeStamp = timer.getTime()
|
|
self:_StartEscorts(true)
|
|
else
|
|
-- should not happen
|
|
self:E("**** AWACS group dead at onafterEscortShiftChange!")
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterAwacsShiftChange
|
|
-- @param #AWACS self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AWACS self
|
|
function AWACS:onafterAwacsShiftChange(From,Event,To)
|
|
self:I({From, Event, To})
|
|
-- request new Escorts, check if AWACS-FG still alive first!
|
|
if self.AwacsFG and self.ShiftChangeAwacsFlag and not self.ShiftChangeAwacsRequested then
|
|
|
|
-- ok we're good to re-request
|
|
self.ShiftChangeAwacsRequested = true
|
|
self.AwacsTimeStamp = timer.getTime()
|
|
|
|
-- set up the AWACS and let it orbit
|
|
local AwacsAW = self.AirWing -- Ops.AirWing#AIRWING
|
|
local mission = AUFTRAG:NewORBIT_RACETRACK(self.OrbitZone:GetCoordinate(),self.AwacsAngels*1000,self.Speed,self.Heading,self.Leg)
|
|
local timeonstation = (self.AwacsTimeOnStation + self.ShiftChangeTime) * 3600
|
|
mission:SetTime(nil,timeonstation)
|
|
|
|
AwacsAW:AddMission(mission)
|
|
|
|
self.AwacsMissionReplacement = mission
|
|
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- On after "FlightOnMission".
|
|
-- @param #AWACS self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP FlightGroup on mission.
|
|
-- @param Ops.Auftrag#AUFTRAG Mission The requested mission.
|
|
-- @return #AWACS self
|
|
function AWACS:onafterFlightOnMission(From, Event, To, FlightGroup, Mission)
|
|
self:I({From, Event, To})
|
|
-- coming back from AW, set up the flight
|
|
if self:Is("Running") then
|
|
self:_StartSettings(FlightGroup,Mission)
|
|
end
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- END AWACS
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
end -- end do
|
|
|
|
--- Testing
|
|
_SETTINGS:SetLocale("en")
|
|
_SETTINGS:SetImperial()
|
|
_SETTINGS:SetPlayerMenuOff()
|
|
|
|
-- We need an AirWing
|
|
local AwacsAW = AIRWING:New("AirForce WH-1","AirForce One")
|
|
AwacsAW:SetReportOn()
|
|
AwacsAW:SetMarker(true)
|
|
AwacsAW:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Kutaisi))
|
|
AwacsAW:SetRespawnAfterDestroyed(900)
|
|
AwacsAW:Start()
|
|
|
|
-- And a couple of Squads
|
|
-- AWACS itself
|
|
local Squad_One = SQUADRON:New("Awacs One",2,"Awacs North")
|
|
Squad_One:AddMissionCapability({AUFTRAG.Type.ORBIT},100)
|
|
Squad_One:SetFuelLowRefuel(false)
|
|
Squad_One:SetFuelLowThreshold(0.2)
|
|
Squad_One:SetTurnoverTime(10,20)
|
|
AwacsAW:AddSquadron(Squad_One)
|
|
AwacsAW:NewPayload("Awacs One One",-1,{AUFTRAG.Type.ORBIT},100)
|
|
|
|
-- Escorts
|
|
local Squad_Two = SQUADRON:New("Escorts",4,"Escorts North")
|
|
Squad_Two:AddMissionCapability({AUFTRAG.Type.ESCORT})
|
|
Squad_Two:SetFuelLowRefuel(true)
|
|
Squad_Two:SetFuelLowThreshold(0.3)
|
|
Squad_Two:SetTurnoverTime(10,20)
|
|
AwacsAW:AddSquadron(Squad_Two)
|
|
AwacsAW:NewPayload("Escorts",-1,{AUFTRAG.Type.ESCORT},100)
|
|
|
|
-- CAP
|
|
local Squad_Three = SQUADRON:New("CAP",2,"CAP North")
|
|
Squad_Three:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT})
|
|
Squad_Three:SetFuelLowRefuel(true)
|
|
Squad_Three:SetFuelLowThreshold(0.3)
|
|
Squad_Three:SetTurnoverTime(10,20)
|
|
AwacsAW:AddSquadron(Squad_Three)
|
|
AwacsAW:NewPayload("Aerial-1-2",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100)
|
|
|
|
|
|
-- We need a secondary AirWing for testing
|
|
local AwacsAW2 = AIRWING:New("AirForce WH-2","AirForce Two")
|
|
AwacsAW2:SetReportOn()
|
|
AwacsAW2:SetMarker(true)
|
|
AwacsAW2:SetAirbase(AIRBASE:FindByName(AIRBASE.Caucasus.Beslan))
|
|
AwacsAW2:SetRespawnAfterDestroyed(900)
|
|
AwacsAW2:Start()
|
|
|
|
-- CAP2
|
|
local Squad_ThreeOne = SQUADRON:New("CAP2",4,"CAP West")
|
|
Squad_ThreeOne:AddMissionCapability({AUFTRAG.Type.ALERT5, AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT})
|
|
Squad_ThreeOne:SetFuelLowRefuel(true)
|
|
Squad_ThreeOne:SetFuelLowThreshold(0.3)
|
|
Squad_ThreeOne:SetTurnoverTime(10,20)
|
|
AwacsAW2:AddSquadron(Squad_ThreeOne)
|
|
AwacsAW2:NewPayload("CAP 2-1",-1,{AUFTRAG.Type.ALERT5,AUFTRAG.Type.CAP, AUFTRAG.Type.GCICAP, AUFTRAG.Type.INTERCEPT},100)
|
|
|
|
-- Get AWACS started
|
|
local testawacs = AWACS:New("AWACS North",AwacsAW,"blue",AIRBASE.Caucasus.Kutaisi,"Awacs Orbit",ZONE:FindByName("NW Zone"),"Anchor One",255,radio.modulation.AM )
|
|
testawacs:SetEscort(2)
|
|
testawacs:SetAwacsDetails(CALLSIGN.AWACS.Darkstar,1,22,230,61,15)
|
|
testawacs:SetSRS("E:\\Program Files\\DCS-SimpleRadio-Standalone","female","en-GB",5010,nil)
|
|
testawacs:AddCAPAirWing(AwacsAW2)
|
|
testawacs:__Start(5)
|