From 28c25816a656336942cd0b3101f060b83d6c79d8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Feb 2023 11:57:28 +0100 Subject: [PATCH 1/9] #AICSAR * Fixed issue with underlying OpsTransport * Added FSM Transition OnAfterPilotUnloaded() * Added options for SRS TTS output - no sound files * Added voice options for downed pilot and operator - these can have different voices --- Moose Development/Moose/Functional/AICSAR.lua | 206 ++++++++++++++++-- 1 file changed, 188 insertions(+), 18 deletions(-) diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index 5db982982..345d49d03 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -147,7 +147,7 @@ -- -- 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) +-- my_aicsar:SetSRSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",270,radio.modulation.AM,nil,5002) -- -- or -- @@ -161,7 +161,7 @@ -- @field #AICSAR AICSAR = { ClassName = "AICSAR", - version = "0.0.8", + version = "0.1.9", lid = "", coalition = coalition.side.BLUE, template = "", @@ -173,7 +173,7 @@ AICSAR = { pilotqueue = {}, pilotindex = 0, helos = {}, - verbose = true, + verbose = false, rescuezoneradius = 200, rescued = {}, autoonoff = true, @@ -194,6 +194,13 @@ AICSAR = { helonumber = 3, gettext = nil, locale ="en", -- default text language + SRSTTSRadio = false, + SRSGoogle = false, + SRSQ = nil, + SRSPilot = nil, + SRSPilotVoice = false, + SRSOperator = nil, + SRSOperatorVoice = false, } -- TODO Messages @@ -203,7 +210,7 @@ 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 ", + PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", PILOTKIA = "Pilot KIA!", HELODOWN = "CSAR Helo Down!", PILOTRESCUED = "Pilot rescued!", @@ -212,7 +219,7 @@ AICSAR.Messages = { 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: ", + PILOTDOWN = "Mayday, mayday, mayday! Pilot abgestürzt: ", PILOTKIA = "Pilot gefallen!", HELODOWN = "CSAR Hubschrauber verloren!", PILOTRESCUED = "Pilot gerettet!", @@ -309,6 +316,9 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- Radio self.SRS = nil self.SRSRadio = false + self.SRSTTSRadio = false + self.SRSGoogle = false + self.SRSQ = nil self.SRSFrequency = 243 self.SRSPath = "\\" self.SRSModulation = radio.modulation.AM @@ -343,6 +353,7 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) self:AddTransition("*", "Status", "*") -- CSAR status update. self:AddTransition("*", "PilotDown", "*") -- Pilot down self:AddTransition("*", "PilotPickedUp", "*") -- Pilot in helo + self:AddTransition("*", "PilotUnloaded", "*") -- Pilot Unloaded from helo self:AddTransition("*", "PilotRescued", "*") -- Pilot Rescued self:AddTransition("*", "PilotKIA", "*") -- Pilot dead self:AddTransition("*", "HeloDown", "*") -- Helo dead @@ -404,6 +415,15 @@ function AICSAR:New(Alias,Coalition,Pilottemplate,Helotemplate,FARP,MASHZone) -- @param #string Event Event. -- @param #string To To state. + --- On after "PilotUnloaded" event. + -- @function [parent=#AICSAR] OnAfterPilotUnloaded + -- @param #AICSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Ops.FlightGroup#FLIGHTGROUP Helo + -- @param Ops.OpsGroup#OPSGROUP OpsGroup + --- On after "PilotKIA" event. -- @function [parent=#AICSAR] OnAfterPilotKIA -- @param #AICSAR self @@ -453,7 +473,7 @@ function AICSAR:InitLocalization() return self end ---- [User] Switch sound output on and use SRS +--- [User] Switch sound output on and use SRS output for sound files. -- @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" @@ -464,10 +484,12 @@ end -- @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.SRSTTSRadio = false self.SRSFrequency = Frequency or 243 self.SRSPath = Path or "c:\\" + self.SRS:SetLabel("ACSR") + self.SRS:SetCoalition(self.coalition) 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 @@ -479,6 +501,88 @@ function AICSAR:SetSRSRadio(OnOff,Path,Frequency,Modulation,SoundPath,Port) return self end +--- [User] Switch sound output on and use SRS-TTS output. The voice will be used across all outputs, unless you define an extra voice for downed pilots and/or the operator. +-- See `AICSAR:SetPilotTTSVoice()` and `AICSAR:SetOperatorTTSVoice()` +-- @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 (Optional) Defaults to 243 (guard) +-- @param #number Modulation (Optional) Radio modulation. Defaults to radio.modulation.AM +-- @param #number Port (Optional) Port of the SRS, defaults to 5002. +-- @param #string Voice (Optional) The voice to be used. +-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB" +-- @param #string Gender (Optional) The gender to be used, defaults to "male" +-- @param #string GoogleCredentials (Optional) Path to google credentials +-- @return #AICSAR self +function AICSAR:SetSRSTTSRadio(OnOff,Path,Frequency,Modulation,Port,Voice,Culture,Gender,GoogleCredentials) + self:T(self.lid .. "SetSRSTTSRadio") + self.SRSTTSRadio = OnOff and true + self.SRSRadio = false + self.SRSFrequency = Frequency or 243 + self.SRSPath = Path or "C:\\Program Files\\DCS-SimpleRadio-Standalone" + self.SRSModulation = Modulation or radio.modulation.AM + self.SRSPort = Port or 5002 + if OnOff then + self.SRS = MSRS:New(Path,Frequency,Modulation,1) + self.SRS:SetPort(self.SRSPort) + self.SRS:SetCoalition(self.coalition) + self.SRS:SetLabel("ACSR") + self.SRS:SetVoice(Voice) + self.SRS:SetCulture(Culture) + self.SRS:SetGender(Gender) + if GoogleCredentials then + self.SRS:SetGoogle(GoogleCredentials) + self.SRSGoogle = true + end + self.SRSQ = MSRSQUEUE:New(self.alias) + end + return self +end + +--- [User] Set SRS TTS Voice of downed pilot. `AICSAR:SetSRSTTSRadio()` needs to be set first! +-- @param #AICSAR self +-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft. +-- Specific voices override culture and gender! +-- @param #string Culture (Optional) The culture to be used, defaults to "en-US" +-- @param #string Gender (Optional) The gender to be used, defaults to "male" +-- @return #AICSAR self +function AICSAR:SetPilotTTSVoice(Voice,Culture,Gender) + self:T(self.lid .. "SetPilotTTSVoice") + self.SRSPilotVoice = true + self.SRSPilot = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) + self.SRSPilot:SetCoalition(self.coalition) + self.SRSPilot:SetVoice(Voice) + self.SRSPilot:SetCulture(Culture or "en-US") + self.SRSPilot:SetGender(Gender or "male") + self.SRSPilot:SetLabel("PILOT") + if self.SRS.google then + self.SRSPilot:SetGoogle(self.SRS.google) + end + return self +end + +--- [User] Set SRS TTS Voice of the rescue operator. `AICSAR:SetSRSTTSRadio()` needs to be set first! +-- @param #AICSAR self +-- @param #string Voice The voice to be used, e.g. `MSRS.Voices.Google.Standard.en_US_Standard_J` for Google or `MSRS.Voices.Microsoft.David` for Microsoft. +-- Specific voices override culture and gender! +-- @param #string Culture (Optional) The culture to be used, defaults to "en-GB" +-- @param #string Gender (Optional) The gender to be used, defaults to "female" +-- @return #AICSAR self +function AICSAR:SetOperatorTTSVoice(Voice,Culture,Gender) + self:T(self.lid .. "SetOperatorTTSVoice") + self.SRSOperatorVoice = true + self.SRSOperator = MSRS:New(self.SRSPath,self.SRSFrequency,self.SRSModulation,1) + self.SRSOperator:SetCoalition(self.coalition) + self.SRSOperator:SetVoice(Voice) + self.SRSOperator:SetCulture(Culture or "en-GB") + self.SRSOperator:SetGender(Gender or "female") + self.SRSPilot:SetLabel("RESCUE") + if self.SRS.google then + self.SRSOperator:SetGoogle(self.SRS.google) + 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). @@ -545,13 +649,25 @@ function AICSAR:OnEventLandingAfterEjection(EventData) -- Mayday Message local Text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("PILOTDOWN",self.locale) local text = "" + local setting = {} + setting.MGRS_Accuracy = self.MGRS_Accuracy + local location = _LandingPos:ToStringMGRS(setting) + local msgtxt = Text..location.."!" + location = string.gsub(location,"MGRS ","") + location = string.gsub(location,"%s+","") + location = string.gsub(location,"([%a%d])","%1;") -- "0 5 1 " + location = string.gsub(location,"0","zero") + location = string.gsub(location,"9","niner") + location = "MGRS;"..location + if self.SRSGoogle then + location = string.format("%s",location) + end + text = Text .. location .. "!" + local ttstext = Text .. location .. "! Repeat! "..location 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) + MESSAGE:New(msgtxt,15,"AICSAR"):ToCoalition(self.coalition) + -- MESSAGE:New(msgtxt,15,"AICSAR"):ToLog() end if self.SRSRadio then local sound = SOUNDFILE:New(Soundfile,self.SRSSoundPath,Soundlength) @@ -559,6 +675,12 @@ function AICSAR:OnEventLandingAfterEjection(EventData) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSPilotVoice then + self.SRSQ:NewTransmission(ttstext,nil,self.SRSPilot,nil,1) + else + self.SRSQ:NewTransmission(ttstext,nil,self.SRS,nil,1) + end end end @@ -634,6 +756,10 @@ function AICSAR:_InitMission(Pilot,Index) self:__HeloDown(2,Helo,Index) end + local function AICHeloUnloaded(Helo,OpsGroup) + self:__PilotUnloaded(2,Helo,OpsGroup) + end + function helo:OnAfterLoadingDone(From,Event,To) AICPickedUp(helo,helo:GetCargoGroups(),Index) end @@ -642,6 +768,10 @@ function AICSAR:_InitMission(Pilot,Index) AICHeloDead(helo,Index) end + function helo:OnAfterUnloaded(From,Event,To,OpsGroupCargo) + AICHeloUnloaded(helo,OpsGroupCargo) + end + self.helos[Index] = helo return self @@ -652,7 +782,7 @@ end -- @param Wrapper.Group#GROUP Pilot The pilot to be rescued. -- @return #boolean outcome function AICSAR:_CheckInMashZone(Pilot) - self:T(self.lid .. "_CheckQueue") + self:T(self.lid .. "_CheckInMashZone") if Pilot:IsInZone(self.farpzone) then return true else @@ -696,8 +826,9 @@ end --- [Internal] Check pilot queue for next mission -- @param #AICSAR self +-- @param Ops.OpsGroup#OPSGROUP OpsGroup -- @return #AICSAR self -function AICSAR:_CheckQueue() +function AICSAR:_CheckQueue(OpsGroup) self:T(self.lid .. "_CheckQueue") for _index, _pilot in pairs(self.pilotqueue) do local classname = _pilot.ClassName and _pilot.ClassName or "NONE" @@ -709,8 +840,12 @@ function AICSAR:_CheckQueue() local flightgroup = self.helos[_index] -- Ops.FlightGroup#FLIGHTGROUP -- rescued? if self:_CheckInMashZone(_pilot) then - self:T("Pilot" .. _pilot.GroupName .. " rescued!") - _pilot:Destroy(false) + self:T("Pilot" .. _pilot.GroupName .. " rescued!") + if OpsGroup then + OpsGroup:Despawn(10) + else + _pilot:Destroy(true,10) + end self.pilotqueue[_index] = nil self.rescued[_index] = true self:__PilotRescued(2) @@ -767,7 +902,7 @@ end -- @return #AICSAR self function AICSAR:onafterStatus(From, Event, To) self:T({From, Event, To}) - self:_CheckQueue() + --self:_CheckQueue() self:_CheckHelos() self:__Status(30) return self @@ -814,6 +949,12 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end else local text,Soundfile,Soundlength,Subtitle = self.gettext:GetEntry("INITIALNOTOK",self.locale) @@ -828,8 +969,15 @@ function AICSAR:onafterPilotDown(From, Event, To, Coordinate, InReach) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end end + self:_CheckQueue() return self end @@ -851,7 +999,9 @@ function AICSAR:onafterPilotKIA(From, Event, To) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) - end + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end return self end @@ -875,6 +1025,8 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end local findex = 0 local fhname = Helo:GetName() @@ -927,10 +1079,26 @@ function AICSAR:onafterPilotRescued(From, Event, To) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end return self end +--- [Internal] onafterPilotUnloaded +-- @param #AICSAR self +-- @param #string From +-- @param #string Event +-- @param #string To +-- @param Ops.FlightGroup#FLIGHTGROUP Helo +-- @param Ops.OpsGroup#OPSGROUP OpsGroup +-- @return #AICSAR self +function AICSAR:onafterPilotUnloaded(From, Event, To, Helo, OpsGroup) + self:T({From, Event, To}) + self:_CheckQueue(OpsGroup) + return self +end + --- [Internal] onafterPilotPickedUp -- @param #AICSAR self -- @param #string From @@ -952,6 +1120,8 @@ function AICSAR:onafterPilotPickedUp(From, Event, To, Helo, CargoTable, Index) self.SRS:PlaySoundFile(sound,2) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) + elseif self.SRSTTSRadio then + self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) end local findex = 0 local fhname = Helo:GetName() From 608033f38f3708d656ca2ea7a18434b7b0a06da9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Feb 2023 13:17:49 +0100 Subject: [PATCH 2/9] #AICSAR * Docu additions --- Moose Development/Moose/Functional/AICSAR.lua | 41 ++++++++++++++++--- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/AICSAR.lua b/Moose Development/Moose/Functional/AICSAR.lua index 345d49d03..b1ee470ca 100644 --- a/Moose Development/Moose/Functional/AICSAR.lua +++ b/Moose Development/Moose/Functional/AICSAR.lua @@ -11,6 +11,7 @@ -- * Dedicated MASH zone -- * Some FSM functions to include in your mission scripts -- * Limit number of available helos +-- * SRS voice output via TTS or soundfiles -- -- === -- @@ -20,8 +21,8 @@ -- -- === -- --- ### Author: **applevangelist** --- Last Update April 2022 +-- ### Author: **Applevangelist** +-- Last Update February 2022 -- -- === -- @module Functional.AICSAR @@ -87,10 +88,11 @@ -- 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.limithelos -- limit available number of helos going on mission (defaults to true) -- my_aicsar.helonumber -- number of helos available (default: 3) +-- my_aicsar.verbose -- boolean, set to `true`for message output on-screen -- --- ## Radio options +-- ## Radio output 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: -- @@ -100,7 +102,7 @@ -- 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 +-- PILOTDOWN = "Mayday, mayday, mayday! Pilot down at ", -- note that this will be appended with the position in MGRS -- PILOTKIA = "Pilot KIA!", -- HELODOWN = "CSAR Helo Down!", -- PILOTRESCUED = "Pilot rescued!", @@ -136,8 +138,31 @@ -- }, -- } -- +-- ## Radio output via SRS and Text-To-Speech (TTS) +-- +-- Radio output can be done via SRS and Text-To-Speech. No extra sound files required! +-- [Initially, Have a look at the guide on setting up SRS TTS for Moose](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf). +-- The text from the `AICSAR.Messages` table above is converted on the fly to an .ogg-file, which is then played back via SRS on the selected frequency and mdulation. +-- Hint - the small black window popping up shortly is visible in Single-Player only. +-- +-- To set up AICSAR for SRS TTS output, add e.g. the following to your script: +-- +-- -- setup for google TTS, radio 243 AM, SRS server port 5002 with a google standard-quality voice (google cloud account required) +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Google.Standard.en_US_Standard_D,"en-US","female","C:\\Program Files\\DCS-SimpleRadio-Standalone\\google.json") +-- +-- -- alternatively for MS Desktop TTS (voices need to be installed locally first!) +-- my_aicsar:SetSRSTTSRadio(true,"C:\\Program Files\\DCS-SimpleRadio-Standalone",243,radio.modulation.AM,5002,MSRS.Voices.Microsoft.Hazel,"en-GB","female") +-- +-- -- define a different voice for the downed pilot(s) +-- my_aicsar:SetPilotTTSVoice(MSRS.Voices.Google.Standard.en_AU_Standard_D,"en-AU","male") +-- +-- -- define another voice for the operator +-- my_aicsar:SetOperatorTTSVoice(MSRS.Voices.Google.Standard.en_GB_Standard_A,"en-GB","female") +-- +-- ## Radio output via preproduced soundfiles +-- -- 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. +-- save your sound file inside of the .miz mission file. [Example soundfiles are located on github](https://github.com/FlightControl-Master/MOOSE_SOUND/tree/master/AICSAR) -- -- To customize or localize your texts and sounds, you can take e.g. the following approach to add a German language version: -- @@ -1026,7 +1051,11 @@ function AICSAR:onafterHeloDown(From, Event, To, Helo, Index) elseif self.DCSRadio then self:DCSRadioBroadcast(Soundfile,Soundlength,text) elseif self.SRSTTSRadio then + if self.SRSOperatorVoice then + self.SRSQ:NewTransmission(text,nil,self.SRSOperator,nil,1) + else self.SRSQ:NewTransmission(text,nil,self.SRS,nil,1) + end end local findex = 0 local fhname = Helo:GetName() From 2a3d69d0d5c28e66c76fd680d5d075e814b15786 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 9 Feb 2023 16:11:45 +0100 Subject: [PATCH 3/9] #DETECTION * Docu fixes --- Moose Development/Moose/Functional/Detection.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index dfafcae01..e7f76ef0f 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -474,7 +474,7 @@ do -- DETECTION_BASE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. - -- @param #table DetectedItem The DetectedItem. + -- @param #DetectedItem DetectedItem The DetectedItem data structure. self:AddTransition( "*", "Stop", "Stopped" ) @@ -2478,14 +2478,14 @@ do -- DETECTION_AREAS --- DETECTION_AREAS constructor. -- @param #DETECTION_AREAS self -- @param Core.Set#SET_GROUP DetectionSetGroup The @{Core.Set} of GROUPs in the Forward Air Controller role. - -- @param DCS#Distance DetectionZoneRange The range till which targets are grouped upon the first detected target. + -- @param #number DetectionZoneRange The range in meters within which targets are grouped upon the first detected target. Default 5000m. -- @return #DETECTION_AREAS function DETECTION_AREAS:New( DetectionSetGroup, DetectionZoneRange ) -- Inherits from DETECTION_BASE local self = BASE:Inherit( self, DETECTION_BASE:New( DetectionSetGroup ) ) - self.DetectionZoneRange = DetectionZoneRange + self.DetectionZoneRange = DetectionZoneRange or 5000 self._SmokeDetectedUnits = false self._FlareDetectedUnits = false @@ -2988,4 +2988,3 @@ do -- DETECTION_AREAS end end - From d6aa7ec17ca3957cc821c953b6956134b30ab205 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Feb 2023 10:45:23 +0100 Subject: [PATCH 4/9] #Net * Initial Release --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Wrapper/Net.lua | 298 ++++++++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 300 insertions(+) create mode 100644 Moose Development/Moose/Wrapper/Net.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 46458f046..f2e5568c2 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -47,6 +47,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Net.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua new file mode 100644 index 000000000..e822a176b --- /dev/null +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -0,0 +1,298 @@ +--- **Wrapper** - DCS net functions. +-- +-- Encapsules **multiplayer** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- === +-- +-- ### Author: **applevangelist** +-- +-- === +-- +-- @module Wrapper.Net +-- @image Utils_Profiler.jpg + +do +--- The NET class +-- @type NET +-- @field #string ClassName +-- @field #string Version +-- @extends Core.Base#BASE + +--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- @field #NET +NET = { + ClassName = "NET", + Version = "0.0.2" +} + +--- Instantiate a new NET object. +-- @param #NET self +-- @return #NET self +function NET:New() + -- Inherit base. + local self = BASE:Inherit(self, BASE:New()) -- #NET + return self +end + +--- Send chat message. +-- @param #NET self +-- @param #string Message Message to send +-- @param #boolean ToAll (Optional) +-- @return #NET self +function NET:SendChat(Message,ToAll) + if Message then + net.send_chat(Message, ToAll) + end + return self +end + +--- Find the PlayerID by name +-- @param #NET self +-- @param #string Name The player name whose ID to find +-- @return #number PlayerID or nil +function NET:GetPlayerIdByName(Name) + local playerList = self:GetPlayerList() + for i=1,#playerList do + local playerName = net.get_name(i) + if playerName == Name then + return playerList[i] + end + end + return nil +end + +--- Find the PlayerID from a CLIENT object. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number PlayerID or nil +function NET:GetPlayerIDFromClient(Client) + local name = Client:GetPlayerName() + local id = self:GetPlayerIdByName(name) + return id +end + +--- Send chat message to a specific player. +-- @param #NET self +-- @param #string Message The text message +-- @param Wrapper.Client#CLIENT ToClient Client receiving the message +-- @param Wrapper.Client#CLIENT FromClient (Optional) Client sending the message +-- @return #NET self +function NET:SendChatToPlayer(Message, ToClient, FromClient) + local PlayerId = self:GetPlayerIDFromClient(ToClient) + local FromId = self:GetPlayerIDFromClient(FromClient) + if Message and PlayerId and FromId then + net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId)) + elseif Message and PlayerId then + net.send_chat_to(Message, tonumber(PlayerId)) + end + return self +end + +--- Load a specific mission. +-- @param #NET self +-- @param #string Path and Mission +-- @return #boolean success +-- @usage +-- mynet:LoadMission(lfs.writeDir() .. 'Missions\\' .. 'MyTotallyAwesomeMission.miz') +function NET:LoadMission(Path) + local outcome = false + if Path then + outcome = net.load_mission(Path) + end + return outcome +end + +--- Load next mission. Returns false if at the end of list. +-- @param #NET self +-- @return #boolean success +function NET:LoadNextMission() + local outcome = false + outcome = net.load_next_mission() + return outcome +end + +--- Return a table of players currently connected to the server. +-- @param #NET self +-- @return #table PlayerList +function NET:GetPlayerList() + local plist = nil + plist = net.get_player_list() + return plist +end + +--- Returns the playerID of the local player. Always returns 1 for server. +-- @param #NET self +-- @return #number ID +function NET:GetMyPlayerID() + return net.get_my_player_id() +end + +--- Returns the playerID of the server. Currently always returns 1. +-- @param #NET self +-- @return #number ID +function NET:GetServerID() + return net.get_server_id() +end + +--- Return a table of attributes for a given client. If optional attribute is present, only that value is returned. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client. +-- @param #string Attribute (Optional) The attribute to obtain. List see below. +-- @return #table PlayerInfo or nil if it cannot be found +-- @usage +-- Table holds these attributes: +-- +-- 'id' : playerID +-- 'name' : player name +-- 'side' : 0 - spectators, 1 - red, 2 - blue +-- 'slot' : slotID of the player or +-- 'ping' : ping of the player in ms +-- 'ipaddr': IP address of the player, SERVER ONLY +-- 'ucid' : Unique Client Identifier, SERVER ONLY +-- +function NET:GetPlayerInfo(Client,Attribute) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_player_info(tonumber(PlayerID), Attribute) + else + return nil + end +end + +--- Kicks a player from the server. Can display a message to the user. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #string Message (Optional) The message to send. +-- @return #boolean success +function NET:Kick(Client,Message) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID and tonumber(PlayerID) ~= 1 then + return net.kick(tonumber(PlayerID), Message) + else + return false + end +end + +--- Return a statistic for a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number StatisticID The statistic to obtain +-- @return #number Statistic or nil +-- @usage +-- StatisticIDs are: +-- +-- net.PS_PING (0) - ping (in ms) +-- net.PS_CRASH (1) - number of crashes +-- net.PS_CAR (2) - number of destroyed vehicles +-- net.PS_PLANE (3) - ... planes/helicopters +-- net.PS_SHIP (4) - ... ships +-- net.PS_SCORE (5) - total score +-- net.PS_LAND (6) - number of landings +-- net.PS_EJECT (7) - of ejects +-- +-- mynet:GetPlayerStatistic(Client,7) -- return number of ejects +function NET:GetPlayerStatistic(Client,StatisticID) + local PlayerID = self:GetPlayerIDFromClient(Client) + local stats = StatisticID or 0 + if stats > 7 or stats < 0 then stats = 0 end + if PlayerID then + return net.get_stat(tonumber(PlayerID),stats) + else + return nil + end +end + +--- Return the name of a given client. Same a CLIENT:GetPlayerName(). +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #string Name or nil if not obtainable +function NET:GetName(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_name(tonumber(PlayerID)) + else + return nil + end +end + +--- Returns the SideId and SlotId of a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @return #number SlotID +function NET:GetSlot(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + local side,slot = net.get_slot(tonumber(PlayerID)) + return side,slot + else + return nil,nil + end +end + +--- Force the slot for a specific client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @param #number SlotID Slot number +-- @return #boolean Success +function NET:ForceSlot(Client,SideID,SlotID) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.force_player_slot(tonumber(PlayerID), SideID, SlotID ) + else + return false + end +end + +--- Force a client back to spectators. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #boolean Succes +function NET:ReturnToSpectators(Client) + local outcome = self:ForceSlot(Client,0) + return outcome +end + +--- Converts a lua value to a JSON string. +-- @param #string Lua Anything lua +-- @return #table Json +function NET.Lua2Json(Lua) + return net.lua2json(Lua) +end + +--- Converts a JSON string to a lua value. +-- @param #string Json Anything JSON +-- @return #table Lua +function NET.Lua2Json(Json) + return net.json2lua(Json) +end + +--- Executes a lua string in a given lua environment in the game. +-- @param #NET self +-- @param #string State The state in which to execute - see below. +-- @param #string DoString The lua string to be executed. +-- @return #string Output +-- @usage +-- States are: +-- 'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg - used for configuration settings +-- 'mission': holds current mission +-- 'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API +function NET:DoStringIn(State,DoString) + return net.dostring_in(State,DoString) +end + +--- Write an "INFO" entry to the DCS log file, with the message Message. +-- @param #NET self +-- @param #string Message The message to be logged. +-- @return #NET self +function NET:Log(Message) + net.log(Message) + return self +end + +------------------------------------------------------------------------------- +-- End of NET +------------------------------------------------------------------------------- +end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 6cd862e94..4b0666443 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -44,6 +44,7 @@ Wrapper/Airbase.lua Wrapper/Scenery.lua Wrapper/Marker.lua Wrapper/Weapon.lua +Wrapper/Net.lua Cargo/Cargo.lua Cargo/CargoUnit.lua From 3d5470eb222b7a5fd3245b977f7f33e387d37381 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Feb 2023 11:00:14 +0100 Subject: [PATCH 5/9] #NET * Initial release --- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Wrapper/Net.lua | 298 ++++++++++++++++++++++++ Moose Setup/Moose.files | 1 + 3 files changed, 300 insertions(+) create mode 100644 Moose Development/Moose/Wrapper/Net.lua diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 2d8bee643..1540e2d0d 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -46,6 +46,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Net.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Development/Moose/Wrapper/Net.lua b/Moose Development/Moose/Wrapper/Net.lua new file mode 100644 index 000000000..e822a176b --- /dev/null +++ b/Moose Development/Moose/Wrapper/Net.lua @@ -0,0 +1,298 @@ +--- **Wrapper** - DCS net functions. +-- +-- Encapsules **multiplayer** environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- === +-- +-- ### Author: **applevangelist** +-- +-- === +-- +-- @module Wrapper.Net +-- @image Utils_Profiler.jpg + +do +--- The NET class +-- @type NET +-- @field #string ClassName +-- @field #string Version +-- @extends Core.Base#BASE + +--- Encapsules multiplayer environment scripting functions from [net](https://wiki.hoggitworld.com/view/DCS_singleton_net) +-- +-- @field #NET +NET = { + ClassName = "NET", + Version = "0.0.2" +} + +--- Instantiate a new NET object. +-- @param #NET self +-- @return #NET self +function NET:New() + -- Inherit base. + local self = BASE:Inherit(self, BASE:New()) -- #NET + return self +end + +--- Send chat message. +-- @param #NET self +-- @param #string Message Message to send +-- @param #boolean ToAll (Optional) +-- @return #NET self +function NET:SendChat(Message,ToAll) + if Message then + net.send_chat(Message, ToAll) + end + return self +end + +--- Find the PlayerID by name +-- @param #NET self +-- @param #string Name The player name whose ID to find +-- @return #number PlayerID or nil +function NET:GetPlayerIdByName(Name) + local playerList = self:GetPlayerList() + for i=1,#playerList do + local playerName = net.get_name(i) + if playerName == Name then + return playerList[i] + end + end + return nil +end + +--- Find the PlayerID from a CLIENT object. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number PlayerID or nil +function NET:GetPlayerIDFromClient(Client) + local name = Client:GetPlayerName() + local id = self:GetPlayerIdByName(name) + return id +end + +--- Send chat message to a specific player. +-- @param #NET self +-- @param #string Message The text message +-- @param Wrapper.Client#CLIENT ToClient Client receiving the message +-- @param Wrapper.Client#CLIENT FromClient (Optional) Client sending the message +-- @return #NET self +function NET:SendChatToPlayer(Message, ToClient, FromClient) + local PlayerId = self:GetPlayerIDFromClient(ToClient) + local FromId = self:GetPlayerIDFromClient(FromClient) + if Message and PlayerId and FromId then + net.send_chat_to(Message, tonumber(PlayerId) , tonumber(FromId)) + elseif Message and PlayerId then + net.send_chat_to(Message, tonumber(PlayerId)) + end + return self +end + +--- Load a specific mission. +-- @param #NET self +-- @param #string Path and Mission +-- @return #boolean success +-- @usage +-- mynet:LoadMission(lfs.writeDir() .. 'Missions\\' .. 'MyTotallyAwesomeMission.miz') +function NET:LoadMission(Path) + local outcome = false + if Path then + outcome = net.load_mission(Path) + end + return outcome +end + +--- Load next mission. Returns false if at the end of list. +-- @param #NET self +-- @return #boolean success +function NET:LoadNextMission() + local outcome = false + outcome = net.load_next_mission() + return outcome +end + +--- Return a table of players currently connected to the server. +-- @param #NET self +-- @return #table PlayerList +function NET:GetPlayerList() + local plist = nil + plist = net.get_player_list() + return plist +end + +--- Returns the playerID of the local player. Always returns 1 for server. +-- @param #NET self +-- @return #number ID +function NET:GetMyPlayerID() + return net.get_my_player_id() +end + +--- Returns the playerID of the server. Currently always returns 1. +-- @param #NET self +-- @return #number ID +function NET:GetServerID() + return net.get_server_id() +end + +--- Return a table of attributes for a given client. If optional attribute is present, only that value is returned. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client. +-- @param #string Attribute (Optional) The attribute to obtain. List see below. +-- @return #table PlayerInfo or nil if it cannot be found +-- @usage +-- Table holds these attributes: +-- +-- 'id' : playerID +-- 'name' : player name +-- 'side' : 0 - spectators, 1 - red, 2 - blue +-- 'slot' : slotID of the player or +-- 'ping' : ping of the player in ms +-- 'ipaddr': IP address of the player, SERVER ONLY +-- 'ucid' : Unique Client Identifier, SERVER ONLY +-- +function NET:GetPlayerInfo(Client,Attribute) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_player_info(tonumber(PlayerID), Attribute) + else + return nil + end +end + +--- Kicks a player from the server. Can display a message to the user. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #string Message (Optional) The message to send. +-- @return #boolean success +function NET:Kick(Client,Message) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID and tonumber(PlayerID) ~= 1 then + return net.kick(tonumber(PlayerID), Message) + else + return false + end +end + +--- Return a statistic for a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number StatisticID The statistic to obtain +-- @return #number Statistic or nil +-- @usage +-- StatisticIDs are: +-- +-- net.PS_PING (0) - ping (in ms) +-- net.PS_CRASH (1) - number of crashes +-- net.PS_CAR (2) - number of destroyed vehicles +-- net.PS_PLANE (3) - ... planes/helicopters +-- net.PS_SHIP (4) - ... ships +-- net.PS_SCORE (5) - total score +-- net.PS_LAND (6) - number of landings +-- net.PS_EJECT (7) - of ejects +-- +-- mynet:GetPlayerStatistic(Client,7) -- return number of ejects +function NET:GetPlayerStatistic(Client,StatisticID) + local PlayerID = self:GetPlayerIDFromClient(Client) + local stats = StatisticID or 0 + if stats > 7 or stats < 0 then stats = 0 end + if PlayerID then + return net.get_stat(tonumber(PlayerID),stats) + else + return nil + end +end + +--- Return the name of a given client. Same a CLIENT:GetPlayerName(). +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #string Name or nil if not obtainable +function NET:GetName(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.get_name(tonumber(PlayerID)) + else + return nil + end +end + +--- Returns the SideId and SlotId of a given client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @return #number SlotID +function NET:GetSlot(Client) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + local side,slot = net.get_slot(tonumber(PlayerID)) + return side,slot + else + return nil,nil + end +end + +--- Force the slot for a specific client. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue +-- @param #number SlotID Slot number +-- @return #boolean Success +function NET:ForceSlot(Client,SideID,SlotID) + local PlayerID = self:GetPlayerIDFromClient(Client) + if PlayerID then + return net.force_player_slot(tonumber(PlayerID), SideID, SlotID ) + else + return false + end +end + +--- Force a client back to spectators. +-- @param #NET self +-- @param Wrapper.Client#CLIENT Client The client +-- @return #boolean Succes +function NET:ReturnToSpectators(Client) + local outcome = self:ForceSlot(Client,0) + return outcome +end + +--- Converts a lua value to a JSON string. +-- @param #string Lua Anything lua +-- @return #table Json +function NET.Lua2Json(Lua) + return net.lua2json(Lua) +end + +--- Converts a JSON string to a lua value. +-- @param #string Json Anything JSON +-- @return #table Lua +function NET.Lua2Json(Json) + return net.json2lua(Json) +end + +--- Executes a lua string in a given lua environment in the game. +-- @param #NET self +-- @param #string State The state in which to execute - see below. +-- @param #string DoString The lua string to be executed. +-- @return #string Output +-- @usage +-- States are: +-- 'config': the state in which $INSTALL_DIR/Config/main.cfg is executed, as well as $WRITE_DIR/Config/autoexec.cfg - used for configuration settings +-- 'mission': holds current mission +-- 'export': runs $WRITE_DIR/Scripts/Export.lua and the relevant export API +function NET:DoStringIn(State,DoString) + return net.dostring_in(State,DoString) +end + +--- Write an "INFO" entry to the DCS log file, with the message Message. +-- @param #NET self +-- @param #string Message The message to be logged. +-- @return #NET self +function NET:Log(Message) + net.log(Message) + return self +end + +------------------------------------------------------------------------------- +-- End of NET +------------------------------------------------------------------------------- +end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 4f079b028..607f2c9b0 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -47,6 +47,7 @@ Wrapper/Airbase.lua Wrapper/Scenery.lua Wrapper/Marker.lua Wrapper/Weapon.lua +Wrapper/Net.lua Cargo/Cargo.lua Cargo/CargoUnit.lua From 713a5b067f4521b583d48b01dde8907348ac0a34 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 10 Feb 2023 11:40:15 +0100 Subject: [PATCH 6/9] #PLAYERTASKCONTROLLER * Added `AddAgentSet()` --- Moose Development/Moose/Ops/PlayerTask.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/PlayerTask.lua b/Moose Development/Moose/Ops/PlayerTask.lua index bc8bd2bb8..94901b479 100644 --- a/Moose Development/Moose/Ops/PlayerTask.lua +++ b/Moose Development/Moose/Ops/PlayerTask.lua @@ -96,7 +96,7 @@ PLAYERTASK = { --- PLAYERTASK class version. -- @field #string version -PLAYERTASK.version="0.1.12" +PLAYERTASK.version="0.1.14" --- Generic task condition. -- @type PLAYERTASK.Condition @@ -3526,6 +3526,23 @@ function PLAYERTASKCONTROLLER:AddAgent(Recce) return self end +--- [User] Add agent SET_GROUP to INTEL detection. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. +-- @param #PLAYERTASKCONTROLLER self +-- @param Core.Set#SET_GROUP RecceSet SET_GROUP of agents. +-- @return #PLAYERTASKCONTROLLER self +function PLAYERTASKCONTROLLER:AddAgentSet(RecceSet) + self:T(self.lid.."AddAgentSet") + if self.Intel then + local Set = RecceSet:GetAliveSet() + for _,_Recce in pairs(Set) do + self.Intel:AddAgent(_Recce) + end + else + self:E(self.lid.."*****NO detection has been set up (yet)!") + end + return self +end + --- [User] Set up detection of STATIC objects. You need to set up detection with @{#PLAYERTASKCONTROLLER.SetupIntel}() **before** using this. -- @param #PLAYERTASKCONTROLLER self -- @param #boolean OnOff Set to `true`for on and `false`for off. From d3c34ef04d34e23225098c4b62f6122b3ffd9b56 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:11:21 +0100 Subject: [PATCH 7/9] Update MarkerOps_Base.lua (#1919) --- Moose Development/Moose/Core/MarkerOps_Base.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 2cdb2afb6..b39250c8c 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -17,7 +17,7 @@ -- ### Author: **Applevangelist** -- -- Date: 5 May 2021 --- Last Update: Sep 2022 +-- Last Update: Feb 2023 -- -- === --- @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.0", + version = "0.1.1", debug = false, Casesensitive = true, } @@ -124,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text -- @param Core.Point#COORDINATE Coord Coordinate of the marker. - + -- @param #number idx DCS Marker ID + --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @param #MARKEROPS_BASE self @@ -172,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then From 960f261ddde52a1db89ea9b35f2dc4f424ba1c11 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sat, 11 Feb 2023 22:17:24 +0100 Subject: [PATCH 8/9] Master merge (#1920) * #NET * Initial release * Update MarkerOps_Base.lua (#1919) --- Moose Development/Moose/Core/MarkerOps_Base.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 2cdb2afb6..b39250c8c 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -17,7 +17,7 @@ -- ### Author: **Applevangelist** -- -- Date: 5 May 2021 --- Last Update: Sep 2022 +-- Last Update: Feb 2023 -- -- === --- @@ -50,7 +50,7 @@ MARKEROPS_BASE = { ClassName = "MARKEROPS", Tag = "mytag", Keywords = {}, - version = "0.1.0", + version = "0.1.1", debug = false, Casesensitive = true, } @@ -124,7 +124,8 @@ function MARKEROPS_BASE:New(Tagname,Keywords,Casesensitive) -- @param #string Text The text on the marker -- @param #table Keywords Table of matching keywords found in the Event text -- @param Core.Point#COORDINATE Coord Coordinate of the marker. - + -- @param #number idx DCS Marker ID + --- On after "MarkDeleted" event. Triggered when a Marker is deleted from the F10 map. -- @function [parent=#MARKEROPS_BASE] OnAfterMarkDeleted -- @param #MARKEROPS_BASE self @@ -172,7 +173,7 @@ function MARKEROPS_BASE:OnEventMark(Event) if Eventtext~=nil then if self:_MatchTag(Eventtext) then local matchtable = self:_MatchKeywords(Eventtext) - self:MarkChanged(Eventtext,matchtable,coord) + self:MarkChanged(Eventtext,matchtable,coord,Event.idx) end end elseif Event.id==world.event.S_EVENT_MARK_REMOVED then From 54fb9deb3ed8512cbb3f43266114dec9139dbd1a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 12 Feb 2023 11:38:46 +0100 Subject: [PATCH 9/9] Polygon and line drawings --- Moose Development/Moose/Core/Database.lua | 56 +++++++++++++++++++++-- Moose Development/Moose/Core/Pathline.lua | 25 ++++++++-- 2 files changed, 74 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index a95504bb1..ce668fc57 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -416,10 +416,14 @@ do -- Zones and Pathlines for objectID, objectData in pairs(layerData.objects or {}) do -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) - if objectData.polygonMode and objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then + if objectData.polygonMode and (objectData.polygonMode=="free") and objectData.points and #objectData.points>=4 then + + --- + -- Drawing: Polygon free + --- -- Name of the zone. - local ZoneName=objectData.name or "Unknown Drawing" + local ZoneName=objectData.name or "Unknown free Polygon Drawing" -- Reference point. All other points need to be translated by this. local vec2={x=objectData.mapX, y=objectData.mapY} @@ -442,7 +446,7 @@ do -- Zones and Pathlines table.remove(points, #points) -- Debug output - self:I(string.format("Register ZONE: %s (Polygon drawing with %d vertices)", ZoneName, #points)) + self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)", ZoneName, #points)) -- Create new polygon zone. local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) @@ -455,8 +459,53 @@ do -- Zones and Pathlines -- Add zone. self:AddZone(ZoneName, Zone) + + -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) + elseif objectData.polygonMode and objectData.polygonMode=="rect" then + + --- + -- Drawing: Polygon rect + --- + + -- Name of the zone. + local ZoneName=objectData.name or "Unknown rect Polygon Drawing" + -- Reference point (center of the rectangle). + local vec2={x=objectData.mapX, y=objectData.mapY} + + -- For a rectangular polygon drawing, we have the width (y) and height (x). + local w=objectData.width + local h=objectData.height + + -- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it). + local points={} + points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left + points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right + points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right + points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left + + --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") + + -- Debug output + self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)", ZoneName, #points)) + + -- Create new polygon zone. + local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) + + -- Set color. + Zone:SetColor({1, 0, 0}, 0.15) + + -- Store in DB. + self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone. + self:AddZone(ZoneName, Zone) + elseif objectData.lineMode and (objectData.lineMode=="segments" or objectData.lineMode=="segment" or objectData.lineMode=="free") and objectData.points and #objectData.points>=2 then + + --- + -- Drawing: Line (segments, segment or free) + --- -- Name of the zone. local Name=objectData.name or "Unknown Line Drawing" @@ -472,7 +521,6 @@ do -- Zones and Pathlines local point=_point --DCS#Vec2 points[i]=UTILS.Vec2Add(point, vec2) end - -- Debug output self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)", Name, #points)) diff --git a/Moose Development/Moose/Core/Pathline.lua b/Moose Development/Moose/Core/Pathline.lua index 090da3931..a71da23a3 100644 --- a/Moose Development/Moose/Core/Pathline.lua +++ b/Moose Development/Moose/Core/Pathline.lua @@ -23,17 +23,36 @@ -- @field #table points List of 3D points defining the path. -- @extends Core.Base#BASE ---- *When nothing goes right... Go left!* +--- *The shortest distance between two points is a straight line.* -- Archimedes -- -- === -- -- # The PATHLINE Concept -- --- List of points defining a path from A to B. +-- List of points defining a path from A to B. The pathline can consist of multiple points. Each point holds the information of its position, the surface type, the land height +-- and the water depth (if over sea). -- -- Line drawings created in the mission editor are automatically registered as pathlines and stored in the MOOSE database. -- They can be accessed with the @{#PATHLINE.FindByName) function. -- +-- # Constructor +-- +-- The @{PATHLINE.New) function creates a new PATHLINE object. This does not hold any points. Points can be added with the @{#PATHLINE.AddPointFromVec2} and @{#PATHLINE.AddPointFromVec3} +-- +-- For a given table of 2D or 3D positions, a new PATHLINE object can be created with the @{#PATHLINE.NewFromVec2Array} or @{#PATHLINE.NewFromVec3Array}, respectively. +-- +-- # Line Drawings +-- +-- The most convenient way to create a pathline is the draw panel feature in the DCS mission editor. You can select "Line" and then "Segments", "Segment" or "Free" to draw your lines. +-- These line drawings are then automatically added to the MOOSE database as PATHLINE objects and can be retrieved with the @{#PATHLINE.FindByName) function, where the name is the one +-- you specify in the draw panel. +-- +-- # Mark on F10 map +-- +-- The ponints of the PATHLINE can be marked on the F10 map with the @{#PATHLINE.MarkPoints}(`true`) function. The mark points contain information of the surface type, land height and +-- water depth. +-- +-- To remove the marks, use @{#PATHLINE.MarkPoints}(`false`). -- -- @field #PATHLINE PATHLINE = { @@ -54,7 +73,7 @@ PATHLINE = { --- PATHLINE class version. -- @field #string version -PATHLINE.version="0.0.1" +PATHLINE.version="0.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list