mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
981 lines
35 KiB
Lua
981 lines
35 KiB
Lua
--- **Functional** - AI CSAR system
|
|
--
|
|
-- ## Main Features:
|
|
--
|
|
-- * Send out helicopters to downed pilots
|
|
-- * Rescues players and AI alike
|
|
-- * Coalition specific
|
|
-- * Starting from a FARP or Airbase
|
|
-- * Dedicated MASH zone
|
|
-- * Some FSM functions to include in your mission scripts
|
|
-- * Limit number of available helos
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Example Missions:
|
|
--
|
|
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/CSR-001%20-%20Basics).
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **applevangelist**
|
|
-- Last Update April 2022
|
|
--
|
|
-- ===
|
|
-- @module Functional.AICSAR
|
|
-- @image MOOSE.JPG
|
|
|
|
|
|
|
|
--- AI CSAR class.
|
|
-- @type AICSAR
|
|
-- @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 template Template for pilot.
|
|
-- @field #string helotemplate Template for CSAR helo.
|
|
-- @field #string alias Alias Name.
|
|
-- @field Wrapper.Airbase#AIRBASE farp FARP object from where to start.
|
|
-- @field Core.Zone#ZONE farpzone MASH zone to drop rescued pilots.
|
|
-- @field #number maxdistance Max distance to go for a rescue.
|
|
-- @field #table pilotqueue Queue of pilots to rescue.
|
|
-- @field #number pilotindex Table index to bind pilot to helo.
|
|
-- @field #table helos Table of Ops.FlightGroup#FLIGHTGROUP objects
|
|
-- @field #boolean verbose Switch more output.
|
|
-- @field #number rescuezoneradius Radius around downed pilot for the helo to land in.
|
|
-- @field #table rescued Track number of rescued pilot.
|
|
-- @field #boolean autoonoff Only send a helo when no human heli pilots are available.
|
|
-- @field Core.Set#SET_CLIENT playerset Track if alive heli pilots are available.
|
|
-- @field #boolean limithelos limit available number of helos going on mission (defaults to true)
|
|
-- @field #number helonumber number of helos available (default: 3)
|
|
-- @extends Core.Fsm#FSM
|
|
|
|
|
|
--- *I once donated a pint of my finest red corpuscles to the great American Red Cross and the doctor opined my blood was very helpful; contained so much alcohol they could use it to sterilize their instruments.*
|
|
-- W.C.Fields
|
|
--
|
|
-- ===
|
|
--
|
|
-- # AICSAR Concept
|
|
--
|
|
-- For an AI or human pilot landing with a parachute, a rescue mission will be spawned. The helicopter will fly to the pilot, pick him or her up,
|
|
-- and fly back to a designated MASH (medical) zone, drop the pilot and then return to base.
|
|
-- Operational maxdistance can be set as well as the landing radius around the downed pilot.
|
|
-- Keep in mind that AI helicopters cannot hover-load at the time of writing, so rescue operations over water or in the mountains might not
|
|
-- work.
|
|
-- Optionally, if you have a CSAR operation with human pilots in your mission, you can set AICSAR to ignore missions when human helicopter
|
|
-- pilots are around.
|
|
--
|
|
-- ## Setup
|
|
--
|
|
-- Setup is a one-liner:
|
|
--
|
|
-- -- @param #string Alias Name of this instance.
|
|
-- -- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
|
-- -- @param #string Pilottemplate Pilot template name.
|
|
-- -- @param #string Helotemplate Helicopter template name.
|
|
-- -- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
|
-- -- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
|
-- local my_aicsar=AICSAR:New("Luftrettung",coalition.side.BLUE,"Downed Pilot","Rescue Helo",AIRBASE:FindByName("Test FARP"),ZONE:New("MASH"))
|
|
--
|
|
-- ## Options are
|
|
--
|
|
-- my_aicsar.maxdistance -- maximum operational distance in meters. Defaults to 50NM or 92.6km
|
|
-- my_aicsar.rescuezoneradius -- landing zone around downed pilot. Defaults to 200m
|
|
-- my_aicsar.autoonoff -- stop operations when human helicopter pilots are around. Defaults to true.
|
|
-- my_aicsar.verbose -- text messages to own coalition about ongoing operations. Defaults to true.
|
|
-- my_aicsarlimithelos -- limit available number of helos going on mission (defaults to true)
|
|
-- my_aicsar.helonumber -- number of helos available (default: 3)
|
|
--
|
|
-- ## Radio options
|
|
--
|
|
-- Radio messages, soundfile names and (for SRS) lengths are defined in three enumerators, so you can customize, localize messages and soundfiles to your liking:
|
|
--
|
|
-- Defaults are:
|
|
--
|
|
-- AICSAR.Messages = {
|
|
-- EN = {
|
|
-- INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
|
-- INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
|
-- PILOTDOWN = "Pilot down at ", -- note that this will be appended with the position
|
|
-- PILOTKIA = "Pilot KIA!",
|
|
-- HELODOWN = "CSAR Helo Down!",
|
|
-- PILOTRESCUED = "Pilot rescued!",
|
|
-- PILOTINHELO = "Pilot picked up!",
|
|
-- },
|
|
-- }
|
|
--
|
|
-- Correspondingly, sound file names are defined as these defaults:
|
|
--
|
|
-- AICSAR.RadioMessages = {
|
|
-- EN = {
|
|
-- INITIALOK = "initialok.ogg",
|
|
-- INITIALNOTOK = "initialnotok.ogg",
|
|
-- PILOTDOWN = "pilotdown.ogg",
|
|
-- PILOTKIA = "pilotkia.ogg",
|
|
-- HELODOWN = "helodown.ogg",
|
|
-- PILOTRESCUED = "pilotrescued.ogg",
|
|
-- PILOTINHELO = "pilotinhelo.ogg",
|
|
-- },
|
|
-- }
|
|
--
|
|
-- and these default transmission lengths in seconds:
|
|
--
|
|
-- AICSAR.RadioLength = {
|
|
-- EN = {
|
|
-- INITIALOK = 4.1,
|
|
-- INITIALNOTOK = 4.6,
|
|
-- PILOTDOWN = 2.6,
|
|
-- PILOTKIA = 1.1,
|
|
-- HELODOWN = 2.1,
|
|
-- PILOTRESCUED = 3.5,
|
|
-- PILOTINHELO = 2.6,
|
|
-- },
|
|
-- }
|
|
--
|
|
-- The easiest way to add a soundfile to your mission is to use the "Sound to..." trigger in the mission editor. This will effectively
|
|
-- save your sound file inside of the .miz mission file.
|
|
--
|
|
-- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version:
|
|
--
|
|
-- -- parameters are: locale, ID, text, soundfilename, duration
|
|
-- my_aicsar.gettext:AddEntry("de","INITIALOK","Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind, ein Hubschrauber sammelt Sie auf!","okneu.ogg",5.0)
|
|
-- my_aicsar.locale = "de" -- plays and shows the defined German language texts and sound. Fallback is "en", if something is undefined.
|
|
--
|
|
-- Switch on radio transmissions via **either** SRS **or** "normal" DCS radio e.g. like so:
|
|
--
|
|
-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,5002)
|
|
--
|
|
-- or
|
|
--
|
|
-- my_aicsar:SetDCSRadio(true,300,radio.modulation.AM,GROUP:FindByName("FARP-Radio"))
|
|
--
|
|
-- See the function documentation for parameter details.
|
|
--
|
|
-- ===
|
|
---
|
|
--
|
|
-- @field #AICSAR
|
|
AICSAR = {
|
|
ClassName = "AICSAR",
|
|
version = "0.0.7",
|
|
lid = "",
|
|
coalition = coalition.side.BLUE,
|
|
template = "",
|
|
helotemplate = "",
|
|
alias = "",
|
|
farp = nil,
|
|
farpzone = nil,
|
|
maxdistance = UTILS.NMToMeters(50),
|
|
pilotqueue = {},
|
|
pilotindex = 0,
|
|
helos = {},
|
|
verbose = true,
|
|
rescuezoneradius = 200,
|
|
rescued = {},
|
|
autoonoff = true,
|
|
playerset = nil,
|
|
Messages = {},
|
|
SRS = nil,
|
|
SRSRadio = false,
|
|
SRSFrequency = 243,
|
|
SRSPath = "\\",
|
|
SRSModulation = radio.modulation.AM,
|
|
SRSSoundPath = nil, -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sount to..." in the ME
|
|
SRSPort = 5002,
|
|
DCSRadio = false,
|
|
DCSFrequency = 243,
|
|
DCSModulation = radio.modulation.AM,
|
|
DCSRadioGroup = nil,
|
|
limithelos = true,
|
|
helonumber = 3,
|
|
gettext = nil,
|
|
locale ="en", -- default text language
|
|
}
|
|
|
|
-- TODO Messages
|
|
--- Messages enum
|
|
-- @field Messages
|
|
AICSAR.Messages = {
|
|
EN = {
|
|
INITIALOK = "Roger, Pilot, we hear you. Stay where you are, a helo is on the way!",
|
|
INITIALNOTOK = "Sorry, Pilot. You're behind maximum operational distance! Good Luck!",
|
|
PILOTDOWN = "Pilot down at ",
|
|
PILOTKIA = "Pilot KIA!",
|
|
HELODOWN = "CSAR Helo Down!",
|
|
PILOTRESCUED = "Pilot rescued!",
|
|
PILOTINHELO = "Pilot picked up!",
|
|
},
|
|
DE = {
|
|
INITIALOK = "Copy, Pilot, wir hören Sie. Bleiben Sie, wo Sie sind!\nEin Hubschrauber sammelt Sie auf!",
|
|
INITIALNOTOK = "Verstehe, Pilot. Sie sind zu weit weg von uns.\nViel Glück!",
|
|
PILOTDOWN = "Pilot abgestürzt: ",
|
|
PILOTKIA = "Pilot gefallen!",
|
|
HELODOWN = "CSAR Hubschrauber verloren!",
|
|
PILOTRESCUED = "Pilot gerettet!",
|
|
PILOTINHELO = "Pilot an Bord geholt!",
|
|
},
|
|
}
|
|
|
|
-- TODO Radio Messages
|
|
--- Radio Messages enum for ogg files
|
|
-- @field RadioMessages
|
|
AICSAR.RadioMessages = {
|
|
EN = {
|
|
INITIALOK = "initialok.ogg", -- 4.1 secs
|
|
INITIALNOTOK = "initialnotok.ogg", -- 4.6 secs
|
|
PILOTDOWN = "pilotdown.ogg", -- 2.6 secs
|
|
PILOTKIA = "pilotkia.ogg", -- 1.1 sec
|
|
HELODOWN = "helodown.ogg", -- 2.1 secs
|
|
PILOTRESCUED = "pilotrescued.ogg", -- 3.5 secs
|
|
PILOTINHELO = "pilotinhelo.ogg", -- 2.6 secs
|
|
},
|
|
}
|
|
|
|
-- TODO Radio Messages
|
|
--- Radio Messages enum for ogg files length in secs
|
|
-- @field RadioLength
|
|
AICSAR.RadioLength = {
|
|
EN = {
|
|
INITIALOK = 4.1,
|
|
INITIALNOTOK = 4.6,
|
|
PILOTDOWN = 2.6,
|
|
PILOTKIA = 1.1,
|
|
HELODOWN = 2.1,
|
|
PILOTRESCUED = 3.5,
|
|
PILOTINHELO = 2.6,
|
|
},
|
|
}
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Constructor
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Function to create a new AICSAR object
|
|
-- @param #AICSAR self
|
|
-- @param #string Alias Name of this instance.
|
|
-- @param #number Coalition Coalition as in coalition.side.BLUE, can also be passed as "blue", "red" or "neutral"
|
|
-- @param #string Pilottemplate Pilot template name.
|
|
-- @param #string Helotemplate Helicopter template name.
|
|
-- @param Wrapper.Airbase#AIRBASE FARP FARP object or Airbase from where to start.
|
|
-- @param Core.Zone#ZONE MASHZone Zone where to drop pilots after rescue.
|
|
-- @return #AICSAR self
|
|
function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone)
|
|
-- 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 AICSAR!")
|
|
end
|
|
else
|
|
self.coalition = Coalition
|
|
self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition))
|
|
end
|
|
|
|
-- Set alias.
|
|
if Alias then
|
|
self.alias=tostring(Alias)
|
|
else
|
|
self.alias="Red Cross"
|
|
if self.coalition then
|
|
if self.coalition==coalition.side.RED then
|
|
self.alias="IFRC"
|
|
elseif self.coalition==coalition.side.BLUE then
|
|
self.alias="CSAR"
|
|
end
|
|
end
|
|
end
|
|
|
|
self.template = Pilottemplate
|
|
self.helotemplate = Helotemplate
|
|
self.farp = FARP
|
|
self.farpzone = MASHZone
|
|
self.playerset = SET_CLIENT:New():FilterActive(true):FilterCategories("helicopter"):FilterStart()
|
|
|
|
-- Radio
|
|
self.SRS = nil
|
|
self.SRSRadio = false
|
|
self.SRSFrequency = 243
|
|
self.SRSPath = "\\"
|
|
self.SRSModulation = radio.modulation.AM
|
|
self.SRSSoundPath = nil -- defaults to "l10n/DEFAULT/", i.e. add messages via "Sound to..." in the ME
|
|
self.SRSPort = 5002
|
|
|
|
-- DCS Radio - add messages via "Sound to..." in the ME
|
|
self.DCSRadio = false
|
|
self.DCSFrequency = 243
|
|
self.DCSModulation = radio.modulation.AM
|
|
self.DCSRadioGroup = nil
|
|
self.DCSRadioQueue = nil
|
|
|
|
self.MGRS_Accuracy = 2
|
|
|
|
-- limit number of available helos at the same time
|
|
self.limithelos = true
|
|
self.helonumber = 3
|
|
|
|
-- localization
|
|
self:InitLocalization()
|
|
|
|
-- Set some string id for output to DCS.log file.
|
|
self.lid=string.format("%s (%s) | ", self.alias, 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", "*") -- CSAR status update.
|
|
self:AddTransition("*", "PilotDown", "*") -- Pilot down
|
|
self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo
|
|
self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued
|
|
self:AddTransition("*", "PilotKIA", "*") -- Pilot dead
|
|
self:AddTransition("*", "HeloDown", "*") -- Helo dead
|
|
self:AddTransition("*", "Stop", "Stopped") -- Stop FSM.
|
|
|
|
self:HandleEvent(EVENTS.LandingAfterEjection)
|
|
|
|
self:__Start(math.random(2,5))
|
|
|
|
local text = string.format("%sAICSAR Version %s Starting",self.lid,self.version)
|
|
|
|
self:I(text)
|
|
|
|
------------------------
|
|
--- Pseudo Functions ---
|
|
------------------------
|
|
|
|
--- Triggers the FSM event "Status".
|
|
-- @function [parent=#AICSAR] Status
|
|
-- @param #AICSAR self
|
|
|
|
--- Triggers the FSM event "Status" after a delay.
|
|
-- @function [parent=#AICSAR] __Status
|
|
-- @param #AICSAR self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- Triggers the FSM event "Stop".
|
|
-- @function [parent=#AICSAR] Stop
|
|
-- @param #AICSAR self
|
|
|
|
--- Triggers the FSM event "Stop" after a delay.
|
|
-- @function [parent=#AICSAR] __Stop
|
|
-- @param #AICSAR self
|
|
-- @param #number delay Delay in seconds.
|
|
|
|
--- On after "PilotDown" event.
|
|
-- @function [parent=#AICSAR] OnAfterPilotDown
|
|
-- @param #AICSAR self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
|
|
-- @param #boolean InReach True if in maxdistance else false.
|
|
|
|
--- On after "PilotPickedUp" event.
|
|
-- @function [parent=#AICSAR] OnAfterPilotPickedUp
|
|
-- @param #AICSAR self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
|
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
|
|
-- @param #number Index
|
|
|
|
--- On after "PilotRescued" event.
|
|
-- @function [parent=#AICSAR] OnAfterPilotRescued
|
|
-- @param #AICSAR self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
--- On after "PilotKIA" event.
|
|
-- @function [parent=#AICSAR] OnAfterPilotKIA
|
|
-- @param #AICSAR self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
--- On after "HeloDown" event.
|
|
-- @function [parent=#AICSAR] OnAfterHeloDown
|
|
-- @param #AICSAR self
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
|
-- @param #number Index
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- [Internal] Create the Moose TextAndSoundEntries
|
|
-- @param #AICSAR self
|
|
-- @return #AICSAR self
|
|
function AICSAR:InitLocalization()
|
|
self:T(self.lid .. "InitLocalization")
|
|
-- English standard localization
|
|
self.gettext=TEXTANDSOUND:New(self.ClassName, "en")
|
|
self.gettext:AddEntry("en","INITIALOK",AICSAR.Messages.EN.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
|
|
self.gettext:AddEntry("en","INITIALNOTOK",AICSAR.Messages.EN.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
|
|
self.gettext:AddEntry("en","HELODOWN",AICSAR.Messages.EN.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
|
|
self.gettext:AddEntry("en","PILOTDOWN",AICSAR.Messages.EN.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
|
|
self.gettext:AddEntry("en","PILOTINHELO",AICSAR.Messages.EN.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
|
|
self.gettext:AddEntry("en","PILOTKIA",AICSAR.Messages.EN.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
|
|
self.gettext:AddEntry("en","PILOTRESCUED",AICSAR.Messages.EN.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
|
|
-- German localization - we keep the sound files English
|
|
self.gettext:AddEntry("de","INITIALOK",AICSAR.Messages.DE.INITIALOK,AICSAR.RadioMessages.EN.INITIALOK,AICSAR.RadioLength.INITIALOK)
|
|
self.gettext:AddEntry("de","INITIALNOTOK",AICSAR.Messages.DE.INITIALNOTOK,AICSAR.RadioMessages.EN.INITIALNOTOK,AICSAR.RadioLength.EN.INITIALNOTOK)
|
|
self.gettext:AddEntry("de","HELODOWN",AICSAR.Messages.DE.HELODOWN,AICSAR.RadioMessages.EN.HELODOWN,AICSAR.RadioLength.EN.HELODOWN)
|
|
self.gettext:AddEntry("de","PILOTDOWN",AICSAR.Messages.DE.PILOTDOWN,AICSAR.RadioMessages.EN.PILOTDOWN,AICSAR.RadioLength.EN.PILOTDOWN)
|
|
self.gettext:AddEntry("de","PILOTINHELO",AICSAR.Messages.DE.PILOTINHELO,AICSAR.RadioMessages.EN.PILOTINHELO,AICSAR.RadioLength.EN.PILOTINHELO)
|
|
self.gettext:AddEntry("de","PILOTKIA",AICSAR.Messages.DE.PILOTKIA,AICSAR.RadioMessages.EN.PILOTKIA,AICSAR.RadioLength.EN.PILOTKIA)
|
|
self.gettext:AddEntry("de","PILOTRESCUED",AICSAR.Messages.DE.PILOTRESCUED,AICSAR.RadioMessages.EN.PILOTRESCUED,AICSAR.RadioLength.EN.PILOTRESCUED)
|
|
self.locale = "en"
|
|
return self
|
|
end
|
|
|
|
--- [User] Switch sound output on and use SRS
|
|
-- @param #AICSAR self
|
|
-- @param #boolean OnOff Switch on (true) or off (false).
|
|
-- @param #string Path Path to your SRS Server Component, e.g. "E:\\\\Program Files\\\\DCS-SimpleRadio-Standalone"
|
|
-- @param #number Frequency Defaults to 243 (guard)
|
|
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM
|
|
-- @param #string SoundPath Where to find the audio files. Defaults to nil, i.e. add messages via "Sound to..." in the Mission Editor.
|
|
-- @param #number Port Port of the SRS, defaults to 5002.
|
|
-- @return #AICSAR self
|
|
function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port)
|
|
self:T(self.lid .. "SetSRSRadio")
|
|
self:T(self.lid .. "SetSRSRadio to "..tostring(OnOff))
|
|
self.SRSRadio = OnOff and true
|
|
self.SRSFrequency = Frequency or 243
|
|
self.SRSPath = Path or "c:\\"
|
|
self.SRSModulation = Modulation or radio.modulation.AM
|
|
local soundpath = os.getenv('TMP') .. "\\DCS\\Mission\\l10n\DEFAULT" -- defaults to "l10n/DEFAULT/", i.e. add messages by "Sound to..." in the ME
|
|
self.SRSSoundPath = SoundPath or soundpath
|
|
self.SRSPort = Port or 5002
|
|
if OnOff then
|
|
self.SRS = MSRS:New(Path,Frequency,Modulation)
|
|
self.SRS:SetPort(self.SRSPort)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [User] Switch sound output on and use normale (DCS) radio
|
|
-- @param #AICSAR self
|
|
-- @param #boolean OnOff Switch on (true) or off (false).
|
|
-- @param #number Frequency Defaults to 243 (guard).
|
|
-- @param #number Modulation Radio modulation. Defaults to radio.modulation.AM.
|
|
-- @param Wrapper.Group#GROUP Group The group to use as sending station.
|
|
-- @return #AICSAR self
|
|
function AICSAR:SetDCSRadio(OnOff,Frequency,Modulation,Group)
|
|
self:T(self.lid .. "SetDCSRadio")
|
|
self:T(self.lid .. "SetDCSRadio to "..tostring(OnOff))
|
|
self.DCSRadio = OnOff and true
|
|
self.DCSFrequency = Frequency or 243
|
|
self.DCSModulation = Modulation or radio.modulation.AM
|
|
self.DCSRadioGroup = Group
|
|
if self.DCSRadio then
|
|
self.DCSRadioQueue = RADIOQUEUE:New(Frequency,Modulation,"AI-CSAR")
|
|
self.DCSRadioQueue:Start(5,5)
|
|
self.DCSRadioQueue:SetRadioPower(1000)
|
|
self.DCSRadioQueue:SetSenderCoordinate(Group:GetCoordinate())
|
|
else
|
|
if self.DCSRadioQueue then
|
|
self.DCSRadioQueue:Stop()
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Sound output via non-SRS Radio. Add message files (.ogg) via "Sound to..." in the ME.
|
|
-- @param #AICSAR self
|
|
-- @param #string Soundfile Name of the soundfile
|
|
-- @param #number Duration Duration of the sound
|
|
-- @param #string Subtitle Text to display
|
|
-- @return #AICSAR self
|
|
function AICSAR:DCSRadioBroadcast(Soundfile,Duration,Subtitle)
|
|
self:T(self.lid .. "DCSRadioBroadcast")
|
|
local radioqueue = self.DCSRadioQueue -- Sound.RadioQueue#RADIOQUEUE
|
|
radioqueue:NewTransmission(Soundfile,Duration,nil,2,nil,Subtitle,10)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Catch the landing after ejection and spawn a pilot in situ.
|
|
-- @param #AICSAR self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
-- @return #AICSAR self
|
|
function AICSAR:OnEventLandingAfterEjection(EventData)
|
|
self:T(self.lid .. "OnEventLandingAfterEjection ID=" .. EventData.id)
|
|
|
|
-- autorescue on off?
|
|
if self.autoonoff then
|
|
if self.playerset:CountAlive() > 0 then
|
|
return self
|
|
end
|
|
end
|
|
|
|
local _event = EventData -- Core.Event#EVENTDATA
|
|
-- get position and spawn in a template pilot
|
|
local _LandingPos = COORDINATE:NewFromVec3(_event.initiator:getPosition().p)
|
|
local _country = _event.initiator:getCountry()
|
|
local _coalition = coalition.getCountryCoalition( _country )
|
|
|
|
-- DONE: add distance check
|
|
local distancetofarp = _LandingPos:Get2DDistance(self.farp:GetCoordinate())
|
|
|
|
-- Mayday Message
|
|
local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale)
|
|
local text = ""
|
|
if _coalition == self.coalition then
|
|
if self.verbose then
|
|
local setting = {}
|
|
setting.MGRS_Accuracy = self.MGRS_Accuracy
|
|
local location = _LandingPos:ToStringMGRS(setting)
|
|
text = Text .. location .. "!"
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
end
|
|
|
|
-- further processing
|
|
if _coalition == self.coalition and distancetofarp <= self.maxdistance then
|
|
-- in reach
|
|
self:T(self.lid .. "Spawning new Pilot")
|
|
self.pilotindex = self.pilotindex + 1
|
|
local newpilot = SPAWN:NewWithAlias(self.template,string.format("%s-AICSAR-%d",self.template, self.pilotindex))
|
|
newpilot:InitDelayOff()
|
|
newpilot:OnSpawnGroup(
|
|
function (grp)
|
|
self.pilotqueue[self.pilotindex] = grp
|
|
end
|
|
)
|
|
newpilot:SpawnFromCoordinate(_LandingPos)
|
|
|
|
Unit.destroy(_event.initiator) -- shagrat remove static Pilot model
|
|
self:__PilotDown(2,_LandingPos,true)
|
|
elseif _coalition == self.coalition and distancetofarp > self.maxdistance then
|
|
-- out of reach, apologies, too far off
|
|
self:T(self.lid .. "Pilot out of reach")
|
|
self:__PilotDown(2,_LandingPos,false)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Get FlightGroup
|
|
-- @param #AICSAR self
|
|
-- @return Ops.FlightGroup#FLIGHTGROUP The FlightGroup
|
|
function AICSAR:_GetFlight()
|
|
self:T(self.lid .. "_GetFlight")
|
|
-- Helo Carrier.
|
|
local newhelo = SPAWN:NewWithAlias(self.helotemplate,self.helotemplate..math.random(1,10000))
|
|
:InitDelayOff()
|
|
:InitUnControlled(true)
|
|
:Spawn()
|
|
|
|
local nhelo=FLIGHTGROUP:New(newhelo)
|
|
nhelo:SetHomebase(self.farp)
|
|
nhelo:Activate()
|
|
return nhelo
|
|
end
|
|
|
|
--- [Internal] Create a new rescue mission
|
|
-- @param #AICSAR self
|
|
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
|
-- @param #number Index Index number of this pilot
|
|
-- @return #AICSAR self
|
|
function AICSAR:_InitMission(Pilot,Index)
|
|
self:T(self.lid .. "_InitMission")
|
|
|
|
local pickupzone = ZONE_GROUP:New(Pilot:GetName(),Pilot,self.rescuezoneradius)
|
|
--local pilotset = SET_GROUP:New()
|
|
--pilotset:AddGroup(Pilot)
|
|
|
|
-- Cargo transport assignment.
|
|
local opstransport=OPSTRANSPORT:New(Pilot, pickupzone, self.farpzone)
|
|
|
|
local helo = self:_GetFlight()
|
|
-- inject reservation
|
|
helo.AICSARReserved = true
|
|
|
|
-- Cargo transport assignment to first Huey group.
|
|
helo:AddOpsTransport(opstransport)
|
|
|
|
-- callback functions
|
|
local function AICPickedUp(Helo,Cargo,Index)
|
|
self:__PilotPickedUp(2,Helo,Cargo,Index)
|
|
end
|
|
|
|
local function AICHeloDead(Helo,Index)
|
|
self:__HeloDown(2,Helo,Index)
|
|
end
|
|
|
|
function helo:OnAfterLoadingDone(From,Event,To)
|
|
AICPickedUp(helo,helo:GetCargoGroups(),Index)
|
|
end
|
|
|
|
function helo:OnAfterDead(From,Event,To)
|
|
AICHeloDead(helo,Index)
|
|
end
|
|
|
|
self.helos[Index] = helo
|
|
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Check if pilot arrived in rescue zone (MASH)
|
|
-- @param #AICSAR self
|
|
-- @param Wrapper.Group#GROUP Pilot The pilot to be rescued.
|
|
-- @return #boolean outcome
|
|
function AICSAR:_CheckInMashZone(Pilot)
|
|
self:T(self.lid .. "_CheckQueue")
|
|
if Pilot:IsInZone(self.farpzone) then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- [Internal] Check helo queue
|
|
-- @param #AICSAR self
|
|
-- @return #AICSAR self
|
|
function AICSAR:_CheckHelos()
|
|
self:T(self.lid .. "_CheckHelos")
|
|
for _index,_helo in pairs(self.helos) do
|
|
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
|
if helo and helo.ClassName == "FLIGHTGROUP" then
|
|
local state = helo:GetState()
|
|
local name = helo:GetName()
|
|
self:T("Helo group "..name.." in state "..state)
|
|
if state == "Arrived" then
|
|
helo:__Stop(5)
|
|
self.helos[_index] = nil
|
|
end
|
|
else
|
|
self.helos[_index] = nil
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] Count helos queue
|
|
-- @param #AICSAR self
|
|
-- @return #number Number of helos on mission
|
|
function AICSAR:_CountHelos()
|
|
self:T(self.lid .. "_CountHelos")
|
|
local count = 0
|
|
for _index,_helo in pairs(self.helos) do
|
|
count = count + 1
|
|
end
|
|
return count
|
|
end
|
|
|
|
--- [Internal] Check pilot queue for next mission
|
|
-- @param #AICSAR self
|
|
-- @return #AICSAR self
|
|
function AICSAR:_CheckQueue()
|
|
self:T(self.lid .. "_CheckQueue")
|
|
for _index, _pilot in pairs(self.pilotqueue) do
|
|
local classname = _pilot.ClassName and _pilot.ClassName or "NONE"
|
|
local name = _pilot.GroupName and _pilot.GroupName or "NONE"
|
|
local helocount = self:_CountHelos()
|
|
--self:T("Looking at " .. classname .. " " .. name)
|
|
-- find one w/o mission
|
|
if _pilot and _pilot.ClassName and _pilot.ClassName == "GROUP" then
|
|
local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP
|
|
-- rescued?
|
|
if self:_CheckInMashZone(_pilot) then
|
|
self:T("Pilot" .. _pilot.GroupName .. " rescued!")
|
|
_pilot:Destroy(false)
|
|
self.pilotqueue[_index] = nil
|
|
self.rescued[_index] = true
|
|
self:__PilotRescued(2)
|
|
if flightgroup then
|
|
flightgroup.AICSARReserved = false
|
|
end
|
|
end -- end rescued
|
|
-- has no mission assigned?
|
|
if not _pilot.AICSAR then
|
|
-- helo available?
|
|
if self.limithelos and helocount >= self.helonumber then
|
|
-- none free
|
|
break
|
|
end -- end limit
|
|
_pilot.AICSAR = {}
|
|
_pilot.AICSAR.Status = "Initiated"
|
|
_pilot.AICSAR.Boarded = false
|
|
self:_InitMission(_pilot,_index)
|
|
break
|
|
else
|
|
-- update status from OPSGROUP
|
|
if flightgroup then
|
|
local state = flightgroup:GetState()
|
|
_pilot.AICSAR.Status = state
|
|
end
|
|
--self:T("Flight for " .. _pilot.GroupName .. " in state " .. state)
|
|
end -- end has mission
|
|
end -- end if pilot
|
|
end -- end loop
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- FSM Functions
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- [Internal] onafterStart
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterStart(From, Event, To)
|
|
self:T({From, Event, To})
|
|
self:__Status(3)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterStatus
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterStatus(From, Event, To)
|
|
self:T({From, Event, To})
|
|
self:_CheckQueue()
|
|
self:_CheckHelos()
|
|
self:__Status(30)
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterStop
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterStop(From, Event, To)
|
|
self:T({From, Event, To})
|
|
self:UnHandleEvent(EVENTS.LandingAfterEjection)
|
|
if self.DCSRadioQueue then
|
|
self.DCSRadioQueue:Stop()
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterPilotDown
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Core.Point#COORDINATE Coordinate Location of the pilot.
|
|
-- @param #boolean InReach True if in maxdistance else false.
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach)
|
|
self:T({From, Event, To})
|
|
local CoordinateText = Coordinate:ToStringMGRS()
|
|
local inreach = tostring(InReach)
|
|
--local text = string.format("Pilot down at %s. In reach = %s",CoordinateText,inreach)
|
|
if InReach then
|
|
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALOK",self.locale)
|
|
--local text = AICSAR.Messages.EN.INITIALOK
|
|
self:T(text)
|
|
if self.verbose then
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
else
|
|
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale)
|
|
--local text = AICSAR.Messages.EN.INITIALNOTOK
|
|
self:T(text)
|
|
if self.verbose then
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterPilotKIA
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterPilotKIA(From, Event, To)
|
|
self:T({From, Event, To})
|
|
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTKIA",self.locale)
|
|
if self.verbose then
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterHeloDown
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
|
-- @param #number Index
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterHeloDown(From, Event, To, Helo, Index)
|
|
self:T({From, Event, To})
|
|
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("HELODOWN",self.locale)
|
|
if self.verbose then
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
local findex = 0
|
|
local fhname = Helo:GetName()
|
|
-- find index of Helo
|
|
if Index and Index > 0 then
|
|
findex=Index
|
|
else
|
|
for _index, _helo in pairs(self.helos) do
|
|
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
|
local hname = helo:GetName()
|
|
if fhname == hname then
|
|
findex = _index
|
|
break
|
|
end
|
|
end
|
|
end
|
|
-- find pilot
|
|
if findex > 0 and not self.rescued[findex] then
|
|
local pilot = self.pilotqueue[findex]
|
|
self.helos[findex] = nil
|
|
if pilot.AICSAR.Boarded then
|
|
self:T("Helo Down: Found DEAD Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
|
-- pilot also dead
|
|
self:__PilotKIA(2)
|
|
self.pilotqueue[findex] = nil
|
|
else
|
|
-- initiate new mission
|
|
self:T("Helo Down: Found ALIVE Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
|
self:_InitMission(pilot,findex)
|
|
end
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterPilotRescued
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterPilotRescued(From, Event, To)
|
|
self:T({From, Event, To})
|
|
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTRESCUED",self.locale)
|
|
if self.verbose then
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
return self
|
|
end
|
|
|
|
--- [Internal] onafterPilotPickedUp
|
|
-- @param #AICSAR self
|
|
-- @param #string From
|
|
-- @param #string Event
|
|
-- @param #string To
|
|
-- @param Ops.FlightGroup#FLIGHTGROUP Helo
|
|
-- @param #table CargoTable of Ops.OpsGroup#OPSGROUP Cargo objects
|
|
-- @param #number Index
|
|
-- @return #AICSAR self
|
|
function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index)
|
|
self:T({From, Event, To})
|
|
local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTINHELO",self.locale)
|
|
if self.verbose then
|
|
MESSAGE:New(text,15,"AICSAR"):ToCoalition(self.coalition)
|
|
end
|
|
if self.SRSRadio then
|
|
local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength)
|
|
sound:SetPlayWithSRS(true)
|
|
self.SRS:PlaySoundFile(sound,2)
|
|
elseif self.DCSRadio then
|
|
self:DCSRadioBroadcast(Soundfile,Soundlength,text)
|
|
end
|
|
local findex = 0
|
|
local fhname = Helo:GetName()
|
|
if Index and Index > 0 then
|
|
findex = Index
|
|
else
|
|
-- find index of Helo
|
|
for _index, _helo in pairs(self.helos) do
|
|
local helo = _helo -- Ops.FlightGroup#FLIGHTGROUP
|
|
local hname = helo:GetName()
|
|
if fhname == hname then
|
|
findex = _index
|
|
break
|
|
end
|
|
end
|
|
end
|
|
-- find pilot
|
|
if findex > 0 then
|
|
local pilot = self.pilotqueue[findex]
|
|
self:T("Boarded: Found Pilot ID " .. findex .. " with name " .. pilot:GetName())
|
|
pilot.AICSAR.Boarded = true -- mark as boarded
|
|
end
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-- END AICSAR
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|