From af9324dd5fda66f70b1c297511b734575a9b264c Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 7 Jun 2021 18:26:43 +0200 Subject: [PATCH 01/11] master updates to develop for InitCleanup() and CC Messages (#1545) * Added function for message duration (#1542) ... and correct flash status setting * Update Spawn.lua (#1544) * Update Spawn.lua --- Moose Development/Moose/Core/Spawn.lua | 6 +++--- .../Moose/Tasking/CommandCenter.lua | 20 ++++++++++++++----- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 89d6d681c..d25186b66 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3408,8 +3408,8 @@ function SPAWN:_SpawnCleanUpScheduler() if Stamp.Vec2 then if SpawnUnit:InAir() == false and SpawnUnit:GetVelocityKMH() < 1 then local NewVec2 = SpawnUnit:GetVec2() - if Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y then - -- If the plane is not moving, and is on the ground, assign it with a timestamp... + if (Stamp.Vec2.x == NewVec2.x and Stamp.Vec2.y == NewVec2.y) or (SpawnUnit:GetLife() <= 1) then + -- If the plane is not moving or dead , and is on the ground, assign it with a timestamp... if Stamp.Time + self.SpawnCleanUpInterval < timer.getTime() then self:T( { "CleanUp Scheduler:", "ReSpawning:", SpawnGroup:GetName() } ) self:ReSpawn( SpawnCursor ) @@ -3427,7 +3427,7 @@ function SPAWN:_SpawnCleanUpScheduler() else if SpawnUnit:InAir() == false then Stamp.Vec2 = SpawnUnit:GetVec2() - if SpawnUnit:GetVelocityKMH() < 1 then + if (SpawnUnit:GetVelocityKMH() < 1) then Stamp.Time = timer.getTime() end else diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 0673facb1..2bcfe972c 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -202,6 +202,7 @@ function COMMANDCENTER:New( CommandCenterPositionable, CommandCenterName ) self:SetAutoAcceptTasks( true ) self:SetAutoAssignMethod( COMMANDCENTER.AutoAssignMethods.Distance ) self:SetFlashStatus( false ) + self:SetMessageDuration(10) self:HandleEvent( EVENTS.Birth, --- @param #COMMANDCENTER self @@ -682,7 +683,7 @@ end -- @param #string Message The message text. function COMMANDCENTER:MessageToAll( Message ) - self:GetPositionable():MessageToAll( Message, 20, self:GetName() ) + self:GetPositionable():MessageToAll( Message, self.MessageDuration, self:GetName() ) end @@ -692,7 +693,7 @@ end -- @param Wrapper.Group#GROUP MessageGroup The group to receive the message. function COMMANDCENTER:MessageToGroup( Message, MessageGroup ) - self:GetPositionable():MessageToGroup( Message, 15, MessageGroup, self:GetShortText() ) + self:GetPositionable():MessageToGroup( Message, self.MessageDuration, MessageGroup, self:GetShortText() ) end @@ -715,7 +716,7 @@ function COMMANDCENTER:MessageToCoalition( Message ) local CCCoalition = self:GetPositionable():GetCoalition() --TODO: Fix coalition bug! - self:GetPositionable():MessageToCoalition( Message, 15, CCCoalition, self:GetShortText() ) + self:GetPositionable():MessageToCoalition( Message, self.MessageDuration, CCCoalition, self:GetShortText() ) end @@ -795,9 +796,18 @@ end --- Let the command center flash a report of the status of the subscribed task to a group. -- @param #COMMANDCENTER self +-- @param Flash #boolean function COMMANDCENTER:SetFlashStatus( Flash ) self:F() - self.FlashStatus = Flash or true - + self.FlashStatus = Flash and true +end + +--- Duration a command center message is shown. +-- @param #COMMANDCENTER self +-- @param seconds #number +function COMMANDCENTER:SetMessageDuration(seconds) + self:F() + + self.MessageDuration = 10 or seconds end From 65ed8825b0ef5426f14e2a1f40b430e32a9835ed Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 8 Jun 2021 15:59:36 +0200 Subject: [PATCH 02/11] Update Intelligence.lua Added functionality to calculate the position of a cluster after x seconds, based on trajectory (average speed and heading) of a cluster * INTEL:CalcClusterFuturePosition(cluster,seconds) Will also draw arrows on the map if `self.clustermarkers` is true and `self.verbose > 1` Change cluster coordinate updates to better suite cluster movement --- Moose Development/Moose/Ops/Intelligence.lua | 96 +++++++++++++++++--- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/Ops/Intelligence.lua b/Moose Development/Moose/Ops/Intelligence.lua index 9c4a3a876..7ee0a79fd 100644 --- a/Moose Development/Moose/Ops/Intelligence.lua +++ b/Moose Development/Moose/Ops/Intelligence.lua @@ -35,6 +35,7 @@ -- @field #number clustercounter Running number of clusters. -- @field #number dTforget Time interval in seconds before a known contact which is not detected any more is forgotten. -- @field #number clusterradius Radius im kilometers in which groups/units are considered to belong to a cluster +-- @field #number prediction Seconds default to be used with CalcClusterFuturePosition. -- @extends Core.Fsm#FSM --- Top Secret! @@ -95,6 +96,9 @@ INTEL = { Clusters = {}, clustercounter = 1, clusterradius = 15, + clusteranalysis = true, + clustermarkers = false, + prediction = 300, } --- Detected item info. @@ -131,7 +135,7 @@ INTEL = { --- INTEL class version. -- @field #string version -INTEL.version="0.2.1" +INTEL.version="0.2.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -898,7 +902,7 @@ end -- Cluster Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Paint picture of the battle field. +--- [Internal] Paint picture of the battle field. Does Cluster analysis and updates clusters. Sets markers if markers are enabled. -- @param #INTEL self function INTEL:PaintPicture() @@ -918,9 +922,13 @@ function INTEL:PaintPicture() else local mission = _cluster.mission or nil local marker = _cluster.marker + local markerID = _cluster.markerID if marker then marker:Remove() end + if markerID then + COORDINATE:RemoveMark(markerID) + end self:LostCluster(_cluster, mission) end end @@ -989,12 +997,10 @@ function INTEL:PaintPicture() if self.clustermarkers then for _,_cluster in pairs(self.Clusters) do local cluster=_cluster --#INTEL.Cluster - - local coordinate=self:GetClusterCoordinate(cluster) - - - -- Update F10 marker. - self:UpdateClusterMarker(cluster) + --local coordinate=self:GetClusterCoordinate(cluster) + -- Update F10 marker. + self:UpdateClusterMarker(cluster) + self:CalcClusterFuturePosition(cluster,self.prediction) end end end @@ -1108,6 +1114,7 @@ function INTEL:CalcClusterThreatlevelMax(cluster) local threatlevel=0 for _,_contact in pairs(cluster.Contacts) do + local contact=_contact --#INTEL.Contact if contact.threatlevel>threatlevel then @@ -1119,6 +1126,65 @@ function INTEL:CalcClusterThreatlevelMax(cluster) return threatlevel end +--- Calculate cluster heading. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Heading average of all groups in the cluster. +function INTEL:CalcClusterDirection(cluster) + + local direction = 0 + local n=0 + for _,_contact in pairs(cluster.Contacts) do + local group = _contact.group -- Wrapper.Group#GROUP + if group:IsAlive() then + direction = direction + group:GetHeading() + n=n+1 + end + end + return math.floor(direction / n) + +end + +--- Calculate cluster speed. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @return #number Speed average of all groups in the cluster in MPS. +function INTEL:CalcClusterSpeed(cluster) + + local velocity = 0 + local n=0 + for _,_contact in pairs(cluster.Contacts) do + local group = _contact.group -- Wrapper.Group#GROUP + if group:IsAlive() then + velocity = velocity + group:GetVelocityMPS() + n=n+1 + end + end + return math.floor(velocity / n) + +end + +--- Calculate cluster future position after given seconds. +-- @param #INTEL self +-- @param #INTEL.Cluster cluster The cluster of contacts. +-- @param #number seconds Timeframe in seconds. +-- @return Core.Point#COORDINATE Calculated future position of the cluster. +function INTEL:CalcClusterFuturePosition(cluster,seconds) + local speed = self:CalcClusterSpeed(cluster) -- #number MPS + local direction = self:CalcClusterDirection(cluster) -- #number heading + -- local currposition = cluster.coordinate -- Core.Point#COORDINATE + local currposition = self:GetClusterCoordinate(cluster) -- Core.Point#COORDINATE + local distance = speed * seconds -- #number in meters the cluster will travel + local futureposition = currposition:Translate(distance,direction,true,false) + if self.clustermarkers and (self.verbose > 1) then + if cluster.markerID then + COORDINATE:RemoveMark(cluster.markerID) + end + cluster.markerID = currposition:ArrowToAll(futureposition,self.coalition,{1,0,0},1,{1,1,0},0.5,2,true,"Postion Calc") + end + return futureposition +end + --- Check if contact is in any known cluster. -- @param #INTEL self @@ -1216,10 +1282,16 @@ function INTEL:GetClusterCoordinate(cluster) for _,_contact in pairs(cluster.Contacts) do local contact=_contact --#INTEL.Contact - - x=x+contact.position.x - y=y+contact.position.y - z=z+contact.position.z + local group = contact.group --Wrapper.Group#GROUP + local coord = {} + if group:IsAlive() then + coord = group:GetCoordinate() + else + coord = contact.position + end + x=x+coord.x + y=y+coord.y + z=z+coord.z n=n+1 end From c8d2a7e833368e3434c5e47908920ae86e048820 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 10 Jun 2021 11:59:50 +0200 Subject: [PATCH 03/11] Added commands for immortal and invisible to Wrapper.Group#GROUP (#1547) * Added function for message duration (#1542) ... and correct flash status setting * Update Spawn.lua (#1544) * Update Spawn.lua * Update Group.lua (#1546) Added invisible and immortal commands on GROUP level. --- Moose Development/Moose/Wrapper/Group.lua | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 263b3a0ec..a9c678ab5 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2570,6 +2570,30 @@ function GROUP:EnableEmission(switch) return self end +--- Switch on/off invisible flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandInvisible(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetInvisible', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + +--- Switch on/off immortal flag for the group. +-- @param #GROUP self +-- @param #boolean switch If true, emission is enabled. If false, emission is disabled. +-- @return #GROUP self +function GROUP:SetCommandImmortal(switch) + self:F2( self.GroupName ) + local switch = switch or false + local SetInvisible = {id = 'SetImmortal', params = {value = true}} + self:SetCommand(SetInvisible) + return self +end + --do -- Smoke -- ----- Signal a flare at the position of the GROUP. From 117cf8888ac74b0c6e8e30323f17566def370696 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 13 Jun 2021 10:39:07 +0200 Subject: [PATCH 04/11] Update Globals.lua --- Moose Development/Moose/Globals.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Globals.lua b/Moose Development/Moose/Globals.lua index 21d0c7eaa..f63f57e84 100644 --- a/Moose Development/Moose/Globals.lua +++ b/Moose Development/Moose/Globals.lua @@ -43,4 +43,4 @@ else end if __na then BASE:I("Check /Scripts/MissionScripting.lua and comment out the lines with sanitizeModule(''). Use at your own risk!)") -end \ No newline at end of file +end From 0396741f3da4557d3da05c980c52828878802661 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:18:27 +0200 Subject: [PATCH 05/11] Create CSAR.lua Initial Release --- Moose Development/Moose/Ops/CSAR.lua | 1919 ++++++++++++++++++++++++++ 1 file changed, 1919 insertions(+) create mode 100644 Moose Development/Moose/Ops/CSAR.lua diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua new file mode 100644 index 000000000..9bc98340c --- /dev/null +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -0,0 +1,1919 @@ +--- **Ops** -- Combat Search and Rescue. +-- +-- === +-- +-- **CSAR** - MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ## Missions: +-- +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tbd) +-- +-- === +-- +-- **Main Features:** +-- +-- * MOOSE based Helicopter CSAR Operations. +-- +-- === +-- +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original) +-- @module Ops.CSAR +-- @image OPS_CSAR.jpg + +-- Date: June 2021 + +------------------------------------------------------------------------- +--- **CSAR** class, extends #Core.Base#BASE, #Core.Fsm#FSM +-- @type CSAR +-- @field #string ClassName Name of the class. +-- @field #number verbose Verbosity level. +-- @field #string lid Class id string for output to DCS log file. +-- @field #number coalition Coalition side number, e.g. `coalition.side.RED`. +-- @extends Core.Fsm#FSM + +--- *Combat search and rescue (CSAR) are search and rescue operations that are carried out during war that are within or near combat zones.* (Wikipedia) +-- +-- === +-- +-- ![Banner Image](OPS_CSAR.jpg) +-- +-- # CSAR Concept +-- +-- * Object oriented refactoring of Ciribob's fantastic CSAR script. +-- * No need for extra MIST loading. +-- * Additional events to tailor your mission. +-- +-- ## 0. Prerequisites +-- +-- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. +-- Create a late activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- +-- ## 1. Basic Setup +-- +-- A basic setup example is the following: +-- +-- -- Instantiate and start a CSAR for the blue side, with template "Downed Pilot" and alias "Luftrettung" +-- local my_csar = CSAR:New(coalition.side.BLUE,"Downed Pilot","Luftrettung") +-- -- options +-- my_csar.immortalcrew = true -- downed pilot spawn is immortal +-- my_csar.invisiblevrew = false -- downed pilot spawn is visible +-- -- start the FSM +-- my_csar:__Start(5) +-- +-- ## 2. Options +-- +-- The following options are available (with their defaults): +-- +-- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. +-- self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase. Else MASH only. +-- self.autosmoke = false -- automatically smoke downed pilot location when a heli is near. +-- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. +-- self.csarOncrash = true -- If set to true, will generate a downed pilot when a plane crashes as well. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.enableForAI = true -- set to false to disable AI units from being rescued. +-- self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. +-- self.immortalcrew = true -- Set to true to make wounded crew immortal. +-- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. +-- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. +-- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. +-- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. +-- self.messageTime = 30 -- Time to show longer messages for in seconds. +-- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.template = Template or "generic" -- late activated template for downed pilot, usually single infantry soldier. +-- self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. +-- +-- ## 3. Events +-- +-- The class comes with a number of FSM-based events that missions designers can use to shape their mission. +-- These are: +-- +-- ### 1. PilotDown. +-- +-- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: +-- +-- function my_csar:OnAfterPilotDown(from, event, to, spawnedgroup, frequency, groupname, coordinates_text) +-- ... your code here ... +-- end +-- +-- ### 2. Approach. +-- +-- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: +-- +-- function my_csar:OnAfterApproach(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 3. Boarded. +-- +-- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: +-- +-- function my_csar:OnAfterBoarded(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 4. Returning. +-- +-- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: +-- +-- function my_csar:OnAfterReturning(from, event, to, heliname, groupname) +-- ... your code here ... +-- end +-- +-- ### 5. Rescued. +-- +-- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: +-- +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname) +-- ... your code here ... +-- end +-- +-- ## 4. Spawn downed pilots at location to be picked up. +-- +-- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: +-- +-- -- Create downed "Pilot Wagner" in #ZONE "CSAR_Start_1" at a random point for the blue coalition +-- my_csar:_SpawnCsarAtZone( "CSAR_Start_1", coalition.side.BLUE, "Pilot Wagner", true ) +-- +-- +-- @field #CSAR +CSAR = { + ClassName = "CSAR", + verbose = 1, + lid = "", + coalition = 1, + coalitiontxt = "blue", + FreeVHFFrequencies = {}, + UsedVHFFrequencies = {}, + takenOff = {}, + csarUnits = {}, -- table of unit names + downedPilots = {}, + woundedGroups = {}, + landedStatus = {}, + addedTo = {}, + woundedGroups = {}, -- contains the new group of units + inTransitGroups = {}, -- contain a table for each SAR with all units he has with the original names + smokeMarkers = {}, -- tracks smoke markers for groups + heliVisibleMessage = {}, -- tracks if the first message has been sent of the heli being visible + heliCloseMessage = {}, -- tracks heli close message ie heli < 500m distance + max_units = 6, --number of pilots that can be carried + hoverStatus = {}, -- tracks status of a helis hover above a downed pilot + pilotDisabled = {}, -- tracks what aircraft a pilot is disabled for + pilotLives = {}, -- tracks how many lives a pilot has + useprefix = true, -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + csarPrefix = {}, + template = nil, + bluemash = {}, + smokecolor = 4, + rescues = 0, +} + +--- Downed pilots info. +-- @type CSAR.DownedPilot +-- @field #number index Pilot index. +-- @field #string name Name of the spawned group. +-- @field #number side Coalition. +-- @field #string originalUnit Name of the original unit. +-- @field #string desc Description. +-- @field #string typename Typename of Unit. +-- @field #number frequency Frequency of the NDB. +-- @field #string player Player name if applicable. +-- @field Wrapper.Group#GROUP group Spawned group object. +-- @field #number timestamp Timestamp for approach process + +--- Known beacons from the available maps +-- @field #CSAR.SkipFrequencies +CSAR.SkipFrequencies = { + 745,381,384,300.50,312.5,1175,342,735,300.50,353.00, + 440,795,525,520,690,625,291.5,300.50, + 435,309.50,920,1065,274,312.50, + 580,602,297.50,750,485,950,214, + 1025,730,995,455,307,670,329,395,770, + 380,705,300.5,507,740,1030,515,330,309.5,348,462,905,352,1210,942,435, + 324,320,420,311,389,396,862,680,297.5,920,662,866,907,309.5,822,515,470,342,1182,309.5,720,528, + 337,312.5,830,740,309.5,641,312,722,682,1050, + 1116,935,1000,430,577,540,550,560,570, + } + +--- All slot / Limit settings +-- @type CSAR.AircraftType +-- @field #string typename Unit type name. +CSAR.AircraftType = {} -- Type and limit +CSAR.AircraftType["SA342Mistral"] = 2 +CSAR.AircraftType["SA342Minigun"] = 2 +CSAR.AircraftType["SA342L"] = 4 +CSAR.AircraftType["SA342M"] = 4 +CSAR.AircraftType["UH-1H"] = 4 +CSAR.AircraftType["Mi-8MT"] = 8 +CSAR.AircraftType["Mi-24"] = 8 + +--- CSAR class version. +-- @field #string version +CSAR.version="0.1.0r1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- ToDo list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Documentation +-- WONTDO: Slot blocker etc + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new CSAR object and start the FSM. +-- @param #CSAR self +-- @param #number Coalition Coalition side. Can also be passed as a string "red", "blue" or "neutral". +-- @param #string Template Name of the late activated infantry unit standing in for the downed pilot. +-- @param #string Alias An *optional* alias how this object is called in the logs etc. +-- @return #CSAR self +function CSAR:New(Coalition, Template, Alias) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #CSAR + + --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 CSAR!") + end + else + self.coalition = 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="Спасение" + elseif self.coalition==coalition.side.BLUE then + self.alias="CSAR" + end + end + end + + -- 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", "*") -- Downed Pilot added + self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. + self:AddTransition("*", "Boarded", "*") -- Pilot boarded. + self:AddTransition("*", "Returning", "*") -- CSAR returning to base. + self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. + self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. + + -- tables, mainly for tracking actions + self.addedTo = {} + self.allheligroupset = {} -- GROUP_SET of all helis + self.csarUnits = {} -- table of CSAR unit names + self.FreeVHFFrequencies = {} + self.heliVisibleMessage = {} -- tracks if the first message has been sent of the heli being visible + self.heliCloseMessage = {} -- tracks heli close message ie heli < 500m distance + self.hoverStatus = {} -- tracks status of a helis hover above a downed pilot + self.inTransitGroups = {} -- contain a table for each SAR with all units he has with the original names + self.landedStatus = {} + self.lastCrash = {} + self.takenOff = {} + self.smokeMarkers = {} -- tracks smoke markers for groups + self.UsedVHFFrequencies = {} + self.woundedGroups = {} -- contains the new group of units + self.downedPilots = {} -- Replacement woundedGroups + self.downedpilotcounter = 1 + + -- settings, counters etc + self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. + self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. + self.enableForAI = true -- set to false to disable AI units from being rescued. + self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue + self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.immortalcrew = true -- Set to true to make wounded crew immortal + self.invisiblecrew = false -- Set to true to make wounded crew insvisible + self.messageTime = 30 -- Time to show longer messages for in seconds + self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS + self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. + self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter + self.loadtimemax = 135 -- seconds + self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. If this isnt added to the mission BEACONS WONT WORK! + self.allowFARPRescue = true --allows pilot to be rescued by landing at a FARP or Airbase + self.max_units = 6 --number of pilots that can be carried + self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below + self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DON'T use # in names! + self.template = Template or "generic" -- template for downed pilot + self.mashprefix = {"MASH"} -- prefixes used to find MASHes + self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? + self.autosmoke = false -- automatically smoke location when heli is near + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Start". Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] Start + -- @param #CSAR self + + --- Triggers the FSM event "Start" after a delay. Starts the CSAR. Initializes parameters and starts event handlers. + -- @function [parent=#CSAR] __Start + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". Stops the CSAR and all its event handlers. + -- @param #CSAR self + + --- Triggers the FSM event "Stop" after a delay. Stops the CSAR and all its event handlers. + -- @function [parent=#CSAR] __Stop + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Status". + -- @function [parent=#CSAR] Status + -- @param #CSAR self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#CSAR] __Status + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- On After "PilotDown" event. Downed Pilot detected. + -- @function [parent=#CSAR] OnAfterPilotDown + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Group#GROUP Group Group object of the downed pilot. + -- @param #number Frequency Beacon frequency in kHz. + -- @param #string Leadername Name of the #UNIT of the downed pilot. + -- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. + + --- On After "Aproach" event. Heli close to downed Pilot. + -- @function [parent=#CSAR] OnAfterApproach + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. + + --- On After "Boarded" event. Downed pilot boarded heli. + -- @function [parent=#CSAR] OnAfterBoarded + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group + -- @param #string Woundedgroupname Name of the downed pilot's group + + --- On After "Returning" event. Heli can return home with downed pilot(s). + -- @function [parent=#CSAR] OnAfterReturning + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string Heliname Name of the helicopter group + -- @param #string Woundedgroupname Name of the downed pilot's group + + --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. + -- @function [parent=#CSAR] OnAfterRescued + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter + -- @param #string HeliName Name of the helicopter group + + return self +end + +------------------------ +--- Helper Functions --- +------------------------ + +--- Function to insert downed pilot tracker object. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP Group The #GROUP object +-- @param #string Groupname Name of the spawned group. +-- @param #number Side Coalition. +-- @param #string OriginalUnit Name of original Unit. +-- @param #string Description Descriptive text. +-- @param #string Typename Typename of unit. +-- @param #number Frequency Frequency of the NDB in Hz +-- @param #string Playername Name of Player (if applicable) +-- @return #CSAR self. +function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername) + self:T({"_CreateDownedPilotTrack",Groupname,Side,OriginalUnit,Description,Typename,Frequency,Playername}) + + -- create new entry + local DownedPilot = {} -- #CSAR.DownedPilot + DownedPilot.desc = Description or "" + DownedPilot.frequency = Frequency or 0 + DownedPilot.index = self.downedpilotcounter + DownedPilot.name = Groupname or "" + DownedPilot.originalUnit = OriginalUnit or "" + DownedPilot.player = Playername or "" + DownedPilot.side = Side or 0 + DownedPilot.typename = Typename or "" + DownedPilot.group = Group + DownedPilot.timestamp = 0 + + -- Add Pilot + local PilotTable = self.downedPilots + local counter = self.downedpilotcounter + PilotTable[counter] = {} + PilotTable[counter] = DownedPilot + --self.downedPilots[self.downedpilotcounter]=DownedPilot + self:T({Table=PilotTable}) + self.downedPilots = PilotTable + -- Increase counter + self.downedpilotcounter = self.downedpilotcounter+1 +end + +--- Count pilots on board. +-- @param #CSAR self +-- @param #string _heliName +-- @return #number count +function CSAR:_PilotsOnboard(_heliName) + self:T(self.lid .. " _PilotsOnboard") + local count = 0 + if self.inTransitGroups[_heliName] then + for _, _group in pairs(self.inTransitGroups[_heliName]) do + count = count + 1 + end + end + return count +end + +--- Function to check for dupe eject events. +-- @param #CSAR self +-- @param #string _unitname Name of unit. +-- @return #boolean Outcome +function CSAR:_DoubleEjection(_unitname) + + if self.lastCrash[_unitname] then + local _time = self.lastCrash[_unitname] + + if timer.getTime() - _time < 10 then + self:E(self.lid.."Caught double ejection!") + return true + end + end + + self.lastCrash[_unitname] = timer.getTime() + return false +end + +--- Spawn a downed pilot +-- @param #CSAR self +-- @param #number country Country for template. +-- @param Core.Point#COORDINATE point Coordinate to spawn at. +-- @return Wrapper.Group#GROUP group The #GROUP object. +-- @return #string alias The alias name. +function CSAR:_SpawnPilotInField(country,point) + self:T({country,point}) + local template = self.template + local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local coalition = self.coalition + local pilotcacontrol = self.allowDownedPilotCAcontrol -- is this really correct? + local _spawnedGroup = SPAWN + :NewWithAlias(template,alias) + :InitCoalition(coalition) + :InitCountry(country) + :InitAIOnOff(pilotcacontrol) + :InitDelayOff() + :SpawnFromCoordinate(point) + + --return object + return _spawnedGroup, alias -- Wrapper.Group#GROUP object +end + +--- Add options to a downed pilot +-- @param #CSAR self +-- @param Wrapper.Group#GROUP group Group to use. +function CSAR:_AddSpecialOptions(group) + self:T(self.lid.." _AddSpecialOptions") + self:T({group}) + + local immortalcrew = self.immortalcrew + local invisiblecrew = self.invisiblecrew + if immortalcrew then + local _setImmortal = { + id = 'SetImmortal', + params = { + value = true + } + } + group:SetCommand(_setImmortal) + end + + if invisiblecrew then + -- invisible + local _setInvisible = { + id = 'SetInvisible', + params = { + value = true + } + } + group:SetCommand(_setInvisible) + end + + group:OptionAlarmStateGreen() + group:OptionROEHoldFire() + +end + +--- Function to spawn a CSAR object into the scene. +-- @param #CSAR self +-- @param #number _coalition Coalition +-- @param DCS#country.id _country Country ID +-- @param Core.Point#COORDINATE _point Coordinate +-- @param #string _typeName Typename +-- @param #string _unitName Unitname +-- @param #string _playerName Playername +-- @param #number _freq Frequency +-- @param #boolean noMessage +-- @param #string _description Description +function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) + self:T(self.lid .. " _AddCsar") + self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) + -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) + local template = self.template + local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + local immortalcrew = self.immortalcrew + local invisiblecrew = self.invisiblecrew + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) + local _typeName = _typeName or "PoW" + if not noMessage then + local m = MESSAGE:New("MAYDAY MAYDAY! " .. _typeName .. " is down. ",10,"INFO"):ToCoalition(self.coalition) + end + + if not _freq then + _freq = self:_GenerateADFFrequency() + if not _freq then _freq = "333.25" end --noob catch + end + + if _freq then + self:_AddBeaconToGroup(_spawnedGroup, _freq) + end + + self:_AddSpecialOptions(_spawnedGroup) + -- Generate DESCRIPTION text + local _text = " " + if _playerName ~= nil then + _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName + elseif _typeName ~= nil then + _text = "AI Pilot of " .. _unitName .. " - " .. _typeName + else + _text = _description + end + + + self:T({_spawnedGroup, _alias}) + + local _GroupName = _spawnedGroup:GetName() or _alias + --local _GroupStructure = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + --self.woundedGroups[_GroupName]=_GroupStructure + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) + + --self.woundedGroups[_spawnedGroup:GetName()] = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) + +end + +--- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. +-- @param #CSAR self +-- @param #string _zone Name of the zone. +-- @param #number _coalition Coalition. +-- @param #string _description (optional) Description. +-- @param #boolean _randomPoint (optional) Random yes or no. +-- @param #boolean _nomessage (optional) If true, don't send a message to SAR. +function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _nomessage) + self:T(self.lid .. " _SpawnCsarAtZone") + local freq = self:_GenerateADFFrequency() + local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position + if _triggerZone == nil then + self:E("CSAR - ERROR: Can\'t find zone called " .. _zone, 10) + return + end + + local _description = _description or "none" + + local pos = {} + if _randomPoint then + local _pos = _triggerZone:GetRandomPointVec3() + pos = COORDINATE:NewFromVec3(_pos) + else + pos = _triggerZone:GetCoordinate() + end + + local _country = 0 + if _coalition == coalition.side.BLUE then + _country = country.id.USA + elseif _coalition == coalition.side.RED then + _country = country.id.RUSSIA + else + _country = country.id.UN_PEACEKEEPERS + end + + self:_AddCsar(_coalition, _country, pos, "PoW", "Unknown", nil, freq, _nomessage, _description) +end + +-- TODO: Split in functions per Event type +--- Event handler. +-- @param #CSAR self +function CSAR:_EventHandler(EventData) + self:T(self.lid .. " _EventHandler") + self:T({Event = EventData.id}) + + local _event = EventData -- Core.Event#EVENTDATA + + -- no event + if _event == nil or _event.initiator == nil then + return false + + -- take off + elseif _event.id == EVENTS.Takeoff then -- taken off + self:T(self.lid .. " Event unit - Takeoff") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniGroupName then + self.takenOff[_event.IniUnitName] = true + end + + return true + + -- player enter unit + elseif _event.id == EVENTS.PlayerEnterAircraft or _event.id == EVENTS.PlayerEnterUnit then --player entered unit + self:T(self.lid .. " Event unit - Player Enter") + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + if _event.IniPlayerName then + self.takenOff[_event.IniPlayerName] = nil + end + + -- if its a sar heli, re-add check status script + for _, _heliName in pairs(self.csarUnits) do + if _heliName == _event.IniPlayerName then + -- add back the status script + local DownedPilotTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotTable) do -- #CSAR.DownedPilot + if _groupInfo.side == _event.IniCoalition then + local _woundedName = _groupInfo.name + self:_CheckWoundedGroupStatus(_heliName,_woundedName) + end + end + end + end + + return true + + elseif (_event.id == EVENTS.PilotDead and self.csarOncrash == false) then + -- Pilot dead + + self:T(self.lid .. " Event unit - Pilot Dead") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + -- Catch multiple events here? + if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then + if self:_DoubleEjection(_unitname) then + return + end + + local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) + -- self:_HandleEjectOrCrash(_unit, true) + else + self:T(self.lid .. " Pilot has not taken off, ignore") + end + + return + + elseif _event.id == EVENTS.PilotDead or _event.id == EVENTS.Ejection then + if _event.id == EVENTS.PilotDead and self.csarOncrash == false then + return + end + self:T(self.lid .. " Event unit - Pilot Ejected") + + local _unit = _event.IniUnit + local _unitname = _event.IniUnitName + local _group = _event.IniGroup + + if _unit == nil then + return -- error! + end + + local _coalition = _unit:GetCoalition() + if _coalition ~= self.coalition then + return --ignore! + end + + if self.enableForAI == false and _event.IniPlayerName == nil then + return + end + + if not self.takenOff[_event.IniUnitName] and not _group:IsAirborne() then + self:T(self.lid .. " Pilot has not taken off, ignore") + return -- give up, pilot hasnt taken off + end + + if self:_DoubleEjection(_unitname) then + return + end + + local _freq = self:_GenerateADFFrequency() + self:_AddCsar(_coalition, _unit:GetCountry(), _unit:GetCoordinate() , _unit:GetTypeName(), _unit:GetName(), _event.IniPlayerName, _freq, false, 0) + + return true + + elseif _event.id == EVENTS.Land then + self:T(self.lid .. " Landing") + + if _event.IniUnitName then + self.takenOff[_event.IniUnitName] = nil + end + + if self.allowFARPRescue then + + local _unit = _event.IniUnit -- Wrapper.Unit#UNIT + --local _unit = _event.initiator + + if _unit == nil then + self:T(self.lid .. " Unit nil on landing") + return -- error! + end + + local _coalition = _event.IniCoalition + if _coalition ~= self.coalition then + return --ignore! + end + + self.takenOff[_event.IniUnitName] = nil + + local _place = _event.Place -- Wrapper.Airbase#AIRBASE + + if _place == nil then + self:T(self.lid .. " Landing Place Nil") + return -- error! + end + + if _place:GetCoalition() == self.coalition or _place:GetCoalition() == coalition.side.NEUTRAL then + self:_RescuePilots(_unit) + else + self:T(string.format("Airfield %d, Unit %d", _place:GetCoalition(), _unit:GetCoalition())) + end + end + + return true + end + +end + +--- Initialize the action for a pilot. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _downedGroup The group to rescue. +-- @param #string _GroupName Name of the Group +-- @param #number _freq Beacon frequency. +-- @param #boolean _nomessage Send message true or false. +function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) + self:T(self.lid .. " _InitSARForPilot") + local _leader = _downedGroup:GetUnit(1) + --local _groupName = _downedGroup:GetName() + local _groupName = _GroupName + local _freqk = _freq / 1000 + local _coordinatesText = self:_GetPositionOfWounded(_downedGroup) + local _leadername = _leader:GetName() + + if not _nomessage then + local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) + self:_DisplayToAllSAR(_text) + end + + for _,_heliName in pairs(self.csarUnits) do + self:_CheckWoundedGroupStatus(_heliName, _groupName) + end + + -- trigger FSM event + self:__PilotDown(2,_downedGroup, _freqk, _leadername, _coordinatesText) +end + +--- Check if a name is in downed pilot table +-- @param #CSAR self +-- @param #string name Name to search for. +-- @return #boolean Outcome. +-- @return #CSAR.DownedPilot Table if found else nil. +function CSAR:_CheckNameInDownedPilots(name) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + local table = nil + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + found = true + table = _pilot + break + end + end + return found, table +end + +--- Check if a name is in downed pilot table and remove it. +-- @param #CSAR self +-- @param #string name Name to search for. +-- @param #boolean force Force removal. +-- @return #boolean Outcome. +function CSAR:_RemoveNameFromDownedPilots(name,force) + local PilotTable = self.downedPilots --#CSAR.DownedPilot + local found = false + for _,_pilot in pairs(PilotTable) do + if _pilot.name == name then + local group = _pilot.group -- Wrapper.Group#GROUP + if group then + if (not group:IsAlive()) or ( force == true) then -- don't delete groups which still exist + found = true + _pilot.desc = nil + _pilot.frequency = nil + _pilot.index = nil + _pilot.name = nil + _pilot.originalUnit = nil + _pilot.player = nil + _pilot.side = nil + _pilot.typename = nil + _pilot.group = nil + _pilot.timestamp = nil + end + end + end + end + return found +end + +--- Check state of wounded group. +-- @param #CSAR self +-- @param #string heliname heliname +-- @param #string woundedgroupname woundedgroupname +function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) + self:T(self.lid .. " _CheckWoundedGroupStatus") + local _heliName = heliname + local _woundedGroupName = woundedgroupname + self:T({Heli = _heliName, Downed = _woundedGroupName}) + -- if wounded group is not here then message alread been sent to SARs + -- stop processing any further + local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) + if not _found then + self:T("...not found in list!") + return + end + + --local _woundedGroup = self:_GetWoundedGroup(_woundedGroupName) + --local _woundedGroup = GROUP:FindByName(_woundedGroupName) -- Wrapper.Group#GROUP + local _woundedGroup = _downedpilot.group + if _woundedGroup ~= nil then + local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT + + --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + + local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking + + if _heliUnit == nil then + self.heliVisibleMessage[_lookupKeyHeli] = nil + self.heliCloseMessage[_lookupKeyHeli] = nil + self.landedStatus[_lookupKeyHeli] = nil + self:T("...helinunit nil!") + return + end + + --if self:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) then + local _heliCoord = _heliUnit:GetCoordinate() + local _leaderCoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_heliCoord,_leaderCoord) + if _distance < 3000 and _distance > 0 then + if self:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) == true then + -- we're close, reschedule + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-5,heliname,woundedgroupname) + end + else + self.heliVisibleMessage[_lookupKeyHeli] = nil + --reschedule as units aren't dead yet , schedule for a bit slower though as we're far away + _downedpilot.timestamp = timer.getAbsTime() + self:__Approach(-10,heliname,woundedgroupname) + end + else + self:T("...Downed Pilot KIA?!") + self:_RemoveNameFromDownedPilots(_downedpilot.name) + end +end + +--- Function to pop a smoke at a wounded pilot's positions. +-- @param #CSAR self +-- @param #string _woundedGroupName Name of the group. +-- @param Wrapper.Group#GROUP _woundedLeader Object of the group. +function CSAR:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + self:T(self.lid .. " _PopSmokeForGroup") + -- have we popped smoke already in the last 5 mins + local _lastSmoke = self.smokeMarkers[_woundedGroupName] + if _lastSmoke == nil or timer.getTime() > _lastSmoke then + + local _smokecolor = self.smokecolor + local _smokecoord = _woundedLeader:GetCoordinate() + _smokecoord:Smoke(_smokecolor) + self.smokeMarkers[_woundedGroupName] = timer.getTime() + 300 -- next smoke time + end +end + +--- Function to pickup the wounded pilot from the ground. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit Object of the group. +-- @param #string _pilotName Name of the pilot. +-- @param Wrapper.Group#GROUP _woundedGroup Object of the group. +-- @param #string _woundedGroupName Name of the group. +function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _PickupUnit") + --local _woundedLeader = _woundedGroup:GetUnit(1) + + -- GET IN! + local _heliName = _heliUnit:GetName() + local _groups = self.inTransitGroups[_heliName] + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + + -- init table if there is none for this helicopter + if not _groups then + self.inTransitGroups[_heliName] = {} + _groups = self.inTransitGroups[_heliName] + end + + -- if the heli can't pick them up, show a message and return + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + if _unitsInHelicopter + 1 > _maxUnits then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), 10) + return true + end + + local found,downedgrouptable = self:_CheckNameInDownedPilots(_woundedGroupName) + local grouptable = downedgrouptable --#CSAR.DownedPilot + self.inTransitGroups[_heliName][_woundedGroupName] = + { + -- DONE: Fix with #CSAR.DownedPilot + originalUnit = grouptable.originalUnit, + woundedGroup = _woundedGroupName, + side = self.coalition, + desc = grouptable.desc, + player = grouptable.player, + } + + _woundedGroup:Destroy() + self:_RemoveNameFromDownedPilots(_woundedGroupName,true) + + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10) + + self:__Boarded(5,_heliName,_woundedGroupName) + + return true +end + +--- Move group to destination. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _leader +-- @param Core.Point#COORDINATE _destination +function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) + self:T(self.lid .. " _OrderGroupToMoveToPoint") + local group = _leader + local coordinate = _destination:GetVec2() + --group:RouteGroundTo(_destination,5,"Vee",5) + group:SetAIOn() + group:RouteToVec2(coordinate,5) +end + +--- Function to check if heli is close to group. +-- @param #CSAR self +-- @param #number _distance +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @return #boolean Outcome +function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) + self:T(self.lid .. " _CheckCloseWoundedGroup") + --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + local _woundedLeader = _woundedGroup + local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking + + local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot + local _pilotName = _pilotable.desc + --local _pilotName = self.woundedGroups[_woundedGroupName].desc + --local _pilotName = _woundedGroup:GetName() + + local _reset = true + + if (self.autosmoke == true) and (_distance < 500) then + self:_PopSmokeForGroup(_woundedGroupName, _woundedLeader) + end + + if self.heliVisibleMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime) + end + --mark as shown for THIS heli and THIS group + self.heliVisibleMessage[_lookupKeyHeli] = true + end + + if (_distance < 500) then + + if self.heliCloseMessage[_lookupKeyHeli] == nil then + if self.autosmoke == true then + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), 10) + else + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), 10) + end + --mark as shown for THIS heli and THIS group + self.heliCloseMessage[_lookupKeyHeli] = true + end + + -- have we landed close enough? + if not _heliUnit:InAir() then + + -- if you land on them, doesnt matter if they were heading to someone else as you're closer, you win! :) + if self.pilotRuntoExtractPoint == true then + if (_distance < self.extractDistance) then + local _time = self.landedStatus[_lookupKeyHeli] + if _time == nil then + --self.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) + _time = self.landedStatus[_lookupKeyHeli] + self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true) + else + _time = self.landedStatus[_lookupKeyHeli] - 10 + self.landedStatus[_lookupKeyHeli] = _time + end + if _time <= 0 or _distance < self.loadDistance then + self.landedStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + else + if (_distance < self.loadDistance) then + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + end + else + + local _unitsInHelicopter = self:_PilotsOnboard(_heliName) + local _maxUnits = self.AircraftType[_heliUnit:GetTypeName()] + if _maxUnits == nil then + _maxUnits = self.max_units + end + + if _heliUnit:InAir() and _unitsInHelicopter + 1 <= _maxUnits then + + if _distance < 8.0 then + + --check height! + local leaderheight = _woundedLeader:GetHeight() + if leaderheight < 0 then leaderheight = 0 end + local _height = _heliUnit:GetHeight() - leaderheight + + if _height <= 20.0 then + + local _time = self.hoverStatus[_lookupKeyHeli] + + if _time == nil then + self.hoverStatus[_lookupKeyHeli] = 10 + _time = 10 + else + _time = self.hoverStatus[_lookupKeyHeli] - 10 + self.hoverStatus[_lookupKeyHeli] = _time + end + + if _time > 0 then + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true) + else + self.hoverStatus[_lookupKeyHeli] = nil + self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) + return false + end + _reset = false + else + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true) + end + end + + end + end + end + + if _reset then + self.hoverStatus[_lookupKeyHeli] = nil + end + + if _distance < 500 then + return true + else + return false + end +end + +--- Check if group not KIA. +-- @param #CSAR self +-- @param Wrapper.Group#GROUP _woundedGroup +-- @param #string _woundedGroupName +-- @param Wrapper.Unit#UNIT _heliUnit +-- @param #string _heliName +-- @return #boolean Outcome +function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _heliName) + self:T(self.lid .. " _CheckGroupNotKIA") + -- check if unit has died or been picked up + local inTransit = false + if _woundedGroup and _heliUnit then + for _currentHeli, _groups in pairs(self.inTransitGroups) do + if _groups[_woundedGroupName] then + --local _group = _groups[_woundedGroupName] + inTransit = true + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, _heliName) + break + end -- end name check + end -- end loop + if not inTransit then + -- KIA + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, _heliName) + end + --stops the message being displayed again + self:_RemoveNameFromDownedPilots(_woundedGroupName) + --self.woundedGroups[_woundedGroupName] = nil + end + --continue + return inTransit +end + +--- Monitor in-flight returning groups. +-- @param #CSAR self +-- @param #string heliname Heli name +-- @param #string groupname Group name +function CSAR:_ScheduledSARFlight(heliname,groupname) + self:T(self.lid .. " _ScheduledSARFlight") + self:T({heliname,groupname}) + local _heliUnit = self:_GetSARHeli(heliname) + local _woundedGroupName = groupname + + if (_heliUnit == nil) then + --helicopter crashed? + self.inTransitGroups[heliname] = nil + return + end + + if self.inTransitGroups[heliname] == nil or self.inTransitGroups[heliname][_woundedGroupName] == nil then + -- Groups already rescued + return + end + + local _dist = self:_GetClosestMASH(_heliUnit) + + if _dist == -1 then + return + end + + if _dist < 200 and _heliUnit:InAir() == false then + self:_RescuePilots(_heliUnit) + return + end + + --queue up + self:__Returning(-5,heliname,_woundedGroupName) +end + +--- Mark pilot as rescued and remove from tables. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heliUnit +function CSAR:_RescuePilots(_heliUnit) + self:T(self.lid .. " _RescuePilots") + local _heliName = _heliUnit:GetName() + local _rescuedGroups = self.inTransitGroups[_heliName] + + if _rescuedGroups == nil then + -- Groups already rescued + return + end + + self.inTransitGroups[_heliName] = nil + + local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) + + self:_DisplayMessageToSAR(_heliUnit, _txt, 10) + -- trigger event + self:__Rescued(-1,_heliUnit,_heliName) +end + +--- Check and return Wrappe.Unit#UNIT based on the name if alive. +-- @param #CSAR self +-- @param #string _unitname Name of Unit +-- @return #UNIT or nil +function CSAR:_GetSARHeli(_unitName) + self:T(self.lid .. " _GetSARHeli") + local unit = UNIT:FindByName(_unitName) + if unit and unit:IsAlive() then + return unit + else + return nil + end +end + +--- Display message to single Unit. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _unit +-- @param #string _text +-- @param #number _time +-- @param #boolean _clear +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear) + self:T(self.lid .. " _DisplayMessageToSAR") + local group = _unit:GetGroup() + local _clear = _clear or nil + local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) +end + +--- Function to get string of a group's position. +-- @param #CSAR self +-- @param Wrapper.Controllable#CONTROLLABLE _woundedGroup Group or Unit object. +-- @return #string Coordinates as Text +function CSAR:_GetPositionOfWounded(_woundedGroup) + self:T(self.lid .. " _GetPositionOfWounded") + local _coordinate = _woundedGroup:GetCoordinate() + local _coordinatesText = "None" + if _coordinate then + if self.coordtype == 0 then -- Lat/Long DMTM + _coordinatesText = _coordinate:ToStringLLDDM() + elseif self.coordtype == 1 then -- Lat/Long DMS + _coordinatesText = _coordinate:ToStringLLDMS() + elseif self.coordtype == 2 then -- MGRS + _coordinatesText = _coordinate:ToStringMGRS() + elseif self.coordtype == 3 then -- Bullseye Imperial + local Settings = _SETTINGS:SetImperial() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + else -- Bullseye Metric --(medevac.coordtype == 4) + local Settings = _SETTINGS:SetMetric() + _coordinatesText = _coordinate:ToStringBULLS(self.coalition,Settings) + end + end + return _coordinatesText +end + +--- Display active SAR tasks to player. +-- @param #CSAR self +-- @param #string _unitName Unit to display to +function CSAR:_DisplayActiveSAR(_unitName) + self:T(self.lid .. " _DisplayActiveSAR") + local _msg = "Active MEDEVAC/SAR:" + local _heli = self:_GetSARHeli(_unitName) -- Wrapper.Unit#UNIT + if _heli == nil then + return + end + + local _heliSide = self.coalition + local _csarList = {} + + local _DownedPilotTable = self.downedPilots + self:T({Table=_DownedPilotTable}) + for _, _value in pairs(_DownedPilotTable) do + local _groupName = _value.name + self:T(string.format("Display Active Pilot: %s", tostring(_groupName))) + self:T({Table=_value}) + --local _woundedGroup = GROUP:FindByName(_groupName) + local _woundedGroup = _value.group + if _woundedGroup then + local _coordinatesText = self:_GetPositionOfWounded(_woundedGroup) + local _helicoord = _heli:GetCoordinate() + local _woundcoord = _woundedGroup:GetCoordinate() + local _distance = self:_GetDistance(_helicoord, _woundcoord) + self:T({_distance = _distance}) + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) }) + end + end + + local function sortDistance(a, b) + return a.dist < b.dist + end + + table.sort(_csarList, sortDistance) + + for _, _line in pairs(_csarList) do + _msg = _msg .. "\n" .. _line.msg + end + + self:_DisplayMessageToSAR(_heli, _msg, 20) +end + +--- Find the closest downed pilot to a heli. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @return #table Table of results +function CSAR:_GetClosestDownedPilot(_heli) + self:T(self.lid .. " _GetClosestDownedPilot") + local _side = self.coalition + local _closestGroup = nil + local _shortestDistance = -1 + local _distance = 0 + local _closestGroupInfo = nil + local _heliCoord = _heli:GetCoordinate() + + local DownedPilotsTable = self.downedPilots + for _, _groupInfo in pairs(DownedPilotsTable) do + local _woundedName = _groupInfo.name + --local _tempWounded = GROUP:FindByName(_woundedName) + local _tempWounded = _groupInfo.group + + -- check group exists and not moving to someone else + if _tempWounded then + local _tempCoord = _tempWounded:GetCoordinate() + _distance = self:_GetDistance(_heliCoord, _tempCoord) + + if _distance ~= nil and (_shortestDistance == -1 or _distance < _shortestDistance) then + _shortestDistance = _distance + _closestGroup = _tempWounded + _closestGroupInfo = _groupInfo + end + end + end + + return { pilot = _closestGroup, distance = _shortestDistance, groupInfo = _closestGroupInfo } +end + +--- Fire a flare at the point of a downed pilot. +-- @param #CSAR self +-- @param #string _unitName Name of the unit. +function CSAR:_SignalFlare(_unitName) + self:T(self.lid .. " _SignalFlare") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + + local _closest = self:_GetClosestDownedPilot(_heli) + + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + + local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) + self:_DisplayMessageToSAR(_heli, _msg, 20) + + local _coord = _closest.pilot:GetCoordinate() + _coord:FlareRed(_clockDir) + else + self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + end +end + +--- Display info to all SAR groups. +-- @param #CSAR self +-- @param #string _message +-- @param #number _side +-- @param #string _ignore +function CSAR:_DisplayToAllSAR(_message, _side, _ignore) + self:T(self.lid .. " _DisplayToAllSAR") + for _, _unitName in pairs(self.csarUnits) do + local _unit = self:_GetSARHeli(_unitName) + if _unit then + if not _ignore then + self:_DisplayMessageToSAR(_unit, _message, 10) + end + end + end +end + +---Request smoke at closest downed pilot. +--@param #CSAR self +--@param #string _unitName Name of the helicopter +function CSAR:_Reqsmoke( _unitName ) + self:T(self.lid .. " _Reqsmoke") + local _heli = self:_GetSARHeli(_unitName) + if _heli == nil then + return + end + local _closest = self:_GetClosestDownedPilot(_heli) + if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then + local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) + local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) + self:_DisplayMessageToSAR(_heli, _msg, 20) + local _coord = _closest.pilot:GetCoordinate() + local color = self.smokecolor + _coord:Smoke(color) + else + self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + end +end + +--- Determine distance to closest MASH. +-- @param #CSAR self +-- @param Wrapper.Unit#UNIT _heli Helicopter #UNIT +-- @retunr +function CSAR:_GetClosestMASH(_heli) + self:T(self.lid .. " _GetClosestMASH") + local _mashset = self.bluemash -- Core.Set#SET_GROUP + local _mashes = _mashset:GetSetObjects() -- #table + local _shortestDistance = -1 + local _distance = 0 + local _helicoord = _heli:GetCoordinate() + + local function GetCloseAirbase(coordinate,Coalition,Category) + + local a=coordinate:GetVec3() + local distmin=math.huge + local airbase=nil + for DCSairbaseID, DCSairbase in pairs(world.getAirbases(Coalition)) do + local b=DCSairbase:getPoint() + + local c=UTILS.VecSubstract(a,b) + local dist=UTILS.VecNorm(c) + + if dist 0 then + self:_AddBeaconToGroup(group,frequency) + end + end +end + + ------------------------------ + --- FSM internal Functions --- + ------------------------------ + +--- Function called after Start() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStart(From, Event, To) + self:T({From, Event, To}) + self:I(self.lid .. "Started.") + -- event handler + self:HandleEvent(EVENTS.Takeoff, self._EventHandler) + self:HandleEvent(EVENTS.Land, self._EventHandler) + self:HandleEvent(EVENTS.Ejection, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) + self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) + self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:_GenerateVHFrequencies() + if self.useprefix then + local prefixes = self.csarPrefix or {} + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterPrefixes(prefixes):FilterCategoryHelicopter():FilterStart() + else + self.allheligroupset = SET_GROUP:New():FilterCoalitions(self.coalitiontxt):FilterCategoryHelicopter():FilterStart() + end + self:__Status(-10) + return self +end + +--- Function called before Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onbeforeStatus(From, Event, To) + self:T({From, Event, To}) + -- housekeeping + self:_AddMedevacMenuItem() + self:_RefreshRadioBeacons() + for _,_sar in pairs (self.csarUnits) do + local PilotTable = self.downedPilots + for _,_entry in pairs (PilotTable) do + local entry = _entry -- #CSAR.DownedPilot + local name = entry.name + local timestamp = entry.timestamp or 0 + local now = timer.getAbsTime() + if now - timestamp > 17 then -- only check if we're not in approach mode, which is iterations of 5 and 10. + self:_CheckWoundedGroupStatus(_sar,name) + end + end + end + return self +end + +--- Function called after Status() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStatus(From, Event, To) + self:T({From, Event, To}) + -- collect some stats + local NumberOfSARPilots = 0 + for _, _unitName in pairs(self.csarUnits) do + NumberOfSARPilots = NumberOfSARPilots + 1 + end + + local PilotsInFieldN = 0 + for _, _unitName in pairs(self.downedPilots) do + self:T({_unitName}) + if _unitName.name ~= nil then + PilotsInFieldN = PilotsInFieldN + 1 + end + end + + local PilotsBoarded = 0 + for _, _unitName in pairs(self.inTransitGroups) do + for _,_units in pairs(_unitName) do + PilotsBoarded = PilotsBoarded + 1 + end + end + + if self.verbose > 0 then + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Rescue (landings): %d",self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues) + self:T(text) + if self.verbose > 1 then + local m = MESSAGE:New(text,"10","Status"):ToAll() + end + end + self:__Status(-20) + return self +end + +--- Function called after Stop() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +function CSAR:onafterStop(From, Event, To) + self:T({From, Event, To}) + -- event handler + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.Ejection) + self:UnHandleEvent(EVENTS.PlayerEnterUnit) + self:UnHandleEvent(EVENTS.PlayerEnterAircraft) + self:UnHandleEvent(EVENTS.Dead) + self:T(self.lid .. "Stopped.") + return self +end + +--- Function called before Approach() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeApproach(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_CheckWoundedGroupStatus(Heliname,Woundedgroupname) + return self +end + +--- Function called before Boarded() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeBoarded(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- Function called before Returning() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param #string Heliname Name of the helicopter group. +-- @param #string Woundedgroupname Name of the downed pilot's group. +function CSAR:onbeforeReturning(From, Event, To, Heliname, Woundedgroupname) + self:T({From, Event, To, Heliname, Woundedgroupname}) + self:_ScheduledSARFlight(Heliname,Woundedgroupname) + return self +end + +--- Function called before Rescued() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. +-- @param #string HeliName Name of the helicopter group. +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName) + self:T({From, Event, To, HeliName, HeliUnit}) + self.rescues = self.rescues + 1 + return self +end + +--- Function called before PilotDown() event. +-- @param #CSAR self. +-- @param #string From From state. +-- @param #string Event Event triggered. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group Group object of the downed pilot. +-- @param #number Frequency Beacon frequency in kHz. +-- @param #string Leadername Name of the #UNIT of the downed pilot. +-- @param #string CoordinatesText String of the position of the pilot. Format determined by self.coordtype. +function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, CoordinatesText) + self:T({From, Event, To, Group, Frequency, Leadername, CoordinatesText}) + return self +end +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 7cde279be1e067248ce7171830ed303e1bfbb283 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 13:20:00 +0200 Subject: [PATCH 06/11] Update Modules.lua added CSAR module --- Moose Development/Moose/Modules.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ff11712bf..fef4fc982 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -84,6 +84,7 @@ __Moose.Include( 'Scripts/Moose/Ops/ArmyGroup.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' ) __Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' ) __Moose.Include( 'Scripts/Moose/Ops/Intelligence.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/CSAR.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) From 1b37af321febd383b54268e1d3a705c3b422639b Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 16:21:47 +0200 Subject: [PATCH 07/11] Update CSAR.lua Small updates --- Moose Development/Moose/Ops/CSAR.lua | 44 ++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 9bc98340c..41dc944ab 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -8,7 +8,7 @@ -- -- ## Missions: -- --- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tbd) +-- ### [CSAR - Combat Search & Rescue](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20CSAR) -- -- === -- @@ -127,7 +127,7 @@ -- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: -- --- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname) +-- function my_csar:OnAfterRescued(from, event, to, heliunit, heliname, pilotssaved) -- ... your code here ... -- end -- @@ -212,7 +212,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.0r1" +CSAR.version="0.1.0r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -309,7 +309,7 @@ function CSAR:New(Coalition, Template, Alias) self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = true -- set to false to disable AI units from being rescued. self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue - self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. + self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal self.invisiblecrew = false -- Set to true to make wounded crew insvisible self.messageTime = 30 -- Time to show longer messages for in seconds @@ -383,8 +383,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Heliname Name of the helicopter group - -- @param #string Woundedgroupname Name of the downed pilot's group + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. --- On After "Returning" event. Heli can return home with downed pilot(s). -- @function [parent=#CSAR] OnAfterReturning @@ -392,8 +392,8 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param #string Heliname Name of the helicopter group - -- @param #string Woundedgroupname Name of the downed pilot's group + -- @param #string Heliname Name of the helicopter group. + -- @param #string Woundedgroupname Name of the downed pilot's group. --- On After "Rescued" event. Pilot(s) have been brought to the MASH/FARP/AFB. -- @function [parent=#CSAR] OnAfterRescued @@ -401,8 +401,9 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter - -- @param #string HeliName Name of the helicopter group + -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. + -- @param #string HeliName Name of the helicopter group. + -- @param #number PilotsSaved Number of the save pilots on board when landing. return self end @@ -492,10 +493,13 @@ end -- @return #string alias The alias name. function CSAR:_SpawnPilotInField(country,point) self:T({country,point}) + for i=1,10 do + math.random(i,10000) + end local template = self.template local alias = string.format("Downed Pilot-%d",math.random(1,10000)) local coalition = self.coalition - local pilotcacontrol = self.allowDownedPilotCAcontrol -- is this really correct? + local pilotcacontrol = self.allowDownedPilotCAcontrol -- Switch AI on/oof - is this really correct for CA? local _spawnedGroup = SPAWN :NewWithAlias(template,alias) :InitCoalition(coalition) @@ -559,9 +563,9 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) local template = self.template - local alias = string.format("Downed Pilot-%d",math.random(1,10000)) - local immortalcrew = self.immortalcrew - local invisiblecrew = self.invisiblecrew + --local alias = string.format("Downed Pilot-%d",math.random(1,10000)) + --local immortalcrew = self.immortalcrew + --local invisiblecrew = self.invisiblecrew local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then @@ -1240,13 +1244,20 @@ function CSAR:_RescuePilots(_heliUnit) return end + -- TODO: count saved units? + local PilotsSaved = 0 + for _,_units in pairs(_rescuedGroups) do + PilotsSaved = PilotsSaved + 1 + end + + self.inTransitGroups[_heliName] = nil local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) self:_DisplayMessageToSAR(_heliUnit, _txt, 10) -- trigger event - self:__Rescued(-1,_heliUnit,_heliName) + self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end --- Check and return Wrappe.Unit#UNIT based on the name if alive. @@ -1917,3 +1928,6 @@ function CSAR:onbeforePilotDown(From, Event, To, Group, Frequency, Leadername, C return self end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- End Ops.CSAR +-------------------------------------------------------------------------------------------------------------------------------------------------------------------- + From 10a0793af730dc32b66987eeb4192a9e17543285 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 14 Jun 2021 19:15:58 +0200 Subject: [PATCH 08/11] Update CSAR.lua Small updates to reflect correct measurements. Options to SRS TTS. --- Moose Development/Moose/Ops/CSAR.lua | 191 ++++++++++++++------------- 1 file changed, 96 insertions(+), 95 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 41dc944ab..f95c319da 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -48,7 +48,7 @@ -- ## 0. Prerequisites -- -- You need to load an .ogg soundfile for the pilot's beacons into the mission, e.g. "beacon.ogg", use a once trigger, "sound to country" for that. --- Create a late activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". +-- Create a late-activated single infantry unit as template in the mission editor and name it e.g. "Downed Pilot". -- -- ## 1. Basic Setup -- @@ -79,7 +79,7 @@ -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 30 -- Time to show longer messages for in seconds. +-- self.messageTime = 10 -- Time to show messages for in seconds. Doubled for long messages. -- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. -- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. -- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. @@ -212,13 +212,13 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.0r2" +CSAR.version="0.1.2r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Documentation +-- TODO: SRS Integration (to be tested) -- WONTDO: Slot blocker etc ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -281,7 +281,7 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "PilotDown", "*") -- Downed Pilot added self:AddTransition("*", "Approach", "*") -- CSAR heli closing in. self:AddTransition("*", "Boarded", "*") -- Pilot boarded. - self:AddTransition("*", "Returning", "*") -- CSAR returning to base. + self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. @@ -312,7 +312,7 @@ function CSAR:New(Coalition, Template, Alias) self.coordtype = 2 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. self.immortalcrew = true -- Set to true to make wounded crew immortal self.invisiblecrew = false -- Set to true to make wounded crew insvisible - self.messageTime = 30 -- Time to show longer messages for in seconds + self.messageTime = 15 -- Time to show longer messages for in seconds self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance METERS self.loadDistance = 75 -- configure distance for pilot to get in helicopter in meters. self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter @@ -327,6 +327,14 @@ function CSAR:New(Coalition, Template, Alias) self.bluemash = SET_GROUP:New():FilterCoalitions(self.coalition):FilterPrefixes(self.mashprefix):FilterOnce() -- currently only GROUP objects, maybe support STATICs also? self.autosmoke = false -- automatically smoke location when heli is near + -- WARNING - here'll be dragons + -- for this to work you need to de-sanitize your mission environment in \Scripts\MissionScripting.lua + -- needs SRS => 1.9.6 to work (works on the *server* side) + self.useSRS = false -- Use FF's SRS integration + self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your server(!) + self.SRSchannel = 300 -- radio channel + self.SRSModulation = radio.modulation.AM -- modulation + ------------------------ --- Pseudo Functions --- ------------------------ @@ -403,7 +411,7 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. - -- @param #number PilotsSaved Number of the save pilots on board when landing. + -- @param #number PilotsSaved Number of the saved pilots on board when landing. return self end @@ -444,7 +452,6 @@ function CSAR:_CreateDownedPilotTrack(Group,Groupname,Side,OriginalUnit,Descript local counter = self.downedpilotcounter PilotTable[counter] = {} PilotTable[counter] = DownedPilot - --self.downedPilots[self.downedpilotcounter]=DownedPilot self:T({Table=PilotTable}) self.downedPilots = PilotTable -- Increase counter @@ -471,16 +478,13 @@ end -- @param #string _unitname Name of unit. -- @return #boolean Outcome function CSAR:_DoubleEjection(_unitname) - if self.lastCrash[_unitname] then local _time = self.lastCrash[_unitname] - if timer.getTime() - _time < 10 then self:E(self.lid.."Caught double ejection!") return true end end - self.lastCrash[_unitname] = timer.getTime() return false end @@ -508,7 +512,6 @@ function CSAR:_SpawnPilotInField(country,point) :InitDelayOff() :SpawnFromCoordinate(point) - --return object return _spawnedGroup, alias -- Wrapper.Group#GROUP object end @@ -532,7 +535,6 @@ function CSAR:_AddSpecialOptions(group) end if invisiblecrew then - -- invisible local _setInvisible = { id = 'SetInvisible', params = { @@ -561,11 +563,9 @@ end function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description ) self:T(self.lid .. " _AddCsar") self:T({_coalition , _country, _point, _typeName, _unitName, _playerName, _freq, noMessage, _description}) - -- local _spawnedGroup = self:_SpawnGroup( _coalition, _country, _point, _typeName ) + local template = self.template - --local alias = string.format("Downed Pilot-%d",math.random(1,10000)) - --local immortalcrew = self.immortalcrew - --local invisiblecrew = self.invisiblecrew + local _spawnedGroup, _alias = self:_SpawnPilotInField(_country,_point) local _typeName = _typeName or "PoW" if not noMessage then @@ -582,7 +582,7 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla end self:_AddSpecialOptions(_spawnedGroup) - -- Generate DESCRIPTION text + local _text = " " if _playerName ~= nil then _text = "Pilot " .. _playerName .. " of " .. _unitName .. " - " .. _typeName @@ -591,16 +591,13 @@ function CSAR:_AddCsar(_coalition , _country, _point, _typeName, _unitName, _pla else _text = _description end - - + self:T({_spawnedGroup, _alias}) local _GroupName = _spawnedGroup:GetName() or _alias - --local _GroupStructure = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } - --self.woundedGroups[_GroupName]=_GroupStructure + self:_CreateDownedPilotTrack(_spawnedGroup,_GroupName,_coalition,_unitName,_text,_typeName,_freq,_playerName) - - --self.woundedGroups[_spawnedGroup:GetName()] = { side = _coalition, originalUnit = _unitName, desc = _text, typename = _typeName, frequency = _freq, player = _playerName } + self:_InitSARForPilot(_spawnedGroup, _GroupName, _freq, noMessage) end @@ -617,7 +614,7 @@ function CSAR:_SpawnCsarAtZone( _zone, _coalition, _description, _randomPoint, _ local freq = self:_GenerateADFFrequency() local _triggerZone = ZONE:New(_zone) -- trigger to use as reference position if _triggerZone == nil then - self:E("CSAR - ERROR: Can\'t find zone called " .. _zone, 10) + self:E(self.lid.."ERROR: Can\'t find zone called " .. _zone, 10) return end @@ -722,10 +719,8 @@ function CSAR:_EventHandler(EventData) if self.takenOff[_event.IniUnitName] == true or _group:IsAirborne() then if self:_DoubleEjection(_unitname) then return - end - + end local m = MESSAGE:New("MAYDAY MAYDAY! " .. _unit:GetTypeName() .. " shot down. No Chute!",10,"Info"):ToCoalition(self.coalition) - -- self:_HandleEjectOrCrash(_unit, true) else self:T(self.lid .. " Pilot has not taken off, ignore") end @@ -779,7 +774,6 @@ function CSAR:_EventHandler(EventData) if self.allowFARPRescue then local _unit = _event.IniUnit -- Wrapper.Unit#UNIT - --local _unit = _event.initiator if _unit == nil then self:T(self.lid .. " Unit nil on landing") @@ -829,7 +823,7 @@ function CSAR:_InitSARForPilot(_downedGroup, _GroupName, _freq, _nomessage) if not _nomessage then local _text = string.format("%s requests SAR at %s, beacon at %.2f KHz", _leadername, _coordinatesText, _freqk) - self:_DisplayToAllSAR(_text) + self:_DisplayToAllSAR(_text,self.coalition,self.messageTime) end for _,_heliName in pairs(self.csarUnits) do @@ -899,7 +893,7 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) local _heliName = heliname local _woundedGroupName = woundedgroupname self:T({Heli = _heliName, Downed = _woundedGroupName}) - -- if wounded group is not here then message alread been sent to SARs + -- if wounded group is not here then message already been sent to SARs -- stop processing any further local _found, _downedpilot = self:_CheckNameInDownedPilots(_woundedGroupName) if not _found then @@ -907,14 +901,10 @@ function CSAR:_CheckWoundedGroupStatus(heliname,woundedgroupname) return end - --local _woundedGroup = self:_GetWoundedGroup(_woundedGroupName) - --local _woundedGroup = GROUP:FindByName(_woundedGroupName) -- Wrapper.Group#GROUP local _woundedGroup = _downedpilot.group if _woundedGroup ~= nil then local _heliUnit = self:_GetSARHeli(_heliName) -- Wrapper.Unit#UNIT - --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT - local _lookupKeyHeli = _heliName .. "_" .. _woundedGroupName --lookup key for message state tracking if _heliUnit == nil then @@ -972,9 +962,7 @@ end -- @param #string _woundedGroupName Name of the group. function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) self:T(self.lid .. " _PickupUnit") - --local _woundedLeader = _woundedGroup:GetUnit(1) - - -- GET IN! + -- board local _heliName = _heliUnit:GetName() local _groups = self.inTransitGroups[_heliName] local _unitsInHelicopter = self:_PilotsOnboard(_heliName) @@ -991,7 +979,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _maxUnits = self.max_units end if _unitsInHelicopter + 1 > _maxUnits then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s, %s. We're already crammed with %d guys! Sorry!", _pilotName, _heliName, _unitsInHelicopter, _unitsInHelicopter), self.messageTime) return true end @@ -1010,7 +998,7 @@ function CSAR:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupNam _woundedGroup:Destroy() self:_RemoveNameFromDownedPilots(_woundedGroupName,true) - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s I'm in! Get to the MASH ASAP! ", _heliName, _pilotName), self.messageTime,true,true) self:__Boarded(5,_heliName,_woundedGroupName) @@ -1025,7 +1013,7 @@ function CSAR:_OrderGroupToMoveToPoint(_leader, _destination) self:T(self.lid .. " _OrderGroupToMoveToPoint") local group = _leader local coordinate = _destination:GetVec2() - --group:RouteGroundTo(_destination,5,"Vee",5) + group:SetAIOn() group:RouteToVec2(coordinate,5) end @@ -1040,14 +1028,13 @@ end -- @return #boolean Outcome function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedGroup, _woundedGroupName) self:T(self.lid .. " _CheckCloseWoundedGroup") - --local _woundedLeader = _woundedGroup:GetUnit(1) -- Wrapper.Unit#UNIT + local _woundedLeader = _woundedGroup local _lookupKeyHeli = _heliUnit:GetName() .. "_" .. _woundedGroupName --lookup key for message state tracking local _found, _pilotable = self:_CheckNameInDownedPilots(_woundedGroupName) -- #boolean, #CSAR.DownedPilot local _pilotName = _pilotable.desc - --local _pilotName = self.woundedGroups[_woundedGroupName].desc - --local _pilotName = _woundedGroup:GetName() + local _reset = true @@ -1057,9 +1044,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliVisibleMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Land or hover by the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. I hear you! Damn, that thing is loud! Request a Flare or Smoke if you need", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliVisibleMessage[_lookupKeyHeli] = true @@ -1069,9 +1056,9 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if self.heliCloseMessage[_lookupKeyHeli] == nil then if self.autosmoke == true then - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land or hover at the smoke.", _heliName, _pilotName), self.messageTime,true,true) else - self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), 10) + self:_DisplayMessageToSAR(_heliUnit, string.format("%s: %s. You're close now! Land in a safe place, I will go there ", _heliName, _pilotName), self.messageTime,true,true) end --mark as shown for THIS heli and THIS group self.heliCloseMessage[_lookupKeyHeli] = true @@ -1085,11 +1072,10 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - --self.displayMessageToSAR(_heliUnit, "Landed at " .. _distance, 10, true) self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) - self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. ". Gets in \n" .. _time .. " more seconds.", 10, true) + self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) else _time = self.landedStatus[_lookupKeyHeli] - 10 self.landedStatus[_lookupKeyHeli] = _time @@ -1136,7 +1122,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end if _time > 0 then - self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", 10, true) + self:_DisplayMessageToSAR(_heliUnit, "Hovering above " .. _pilotName .. ". \n\nHold hover for " .. _time .. " seconds to winch them up. \n\nIf the countdown stops you're too far away!", self.messageTime, true) else self.hoverStatus[_lookupKeyHeli] = nil self:_PickupUnit(_heliUnit, _pilotName, _woundedGroup, _woundedGroupName) @@ -1144,7 +1130,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG end _reset = false else - self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", 5, true) + self:_DisplayMessageToSAR(_heliUnit, "Too high to winch " .. _pilotName .. " \nReduce height and hover for 10 seconds!", self.messageTime, true,true) end end @@ -1177,19 +1163,17 @@ function CSAR:_CheckGroupNotKIA(_woundedGroup, _woundedGroupName, _heliUnit, _he if _woundedGroup and _heliUnit then for _currentHeli, _groups in pairs(self.inTransitGroups) do if _groups[_woundedGroupName] then - --local _group = _groups[_woundedGroupName] inTransit = true - self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, _heliName) + self:_DisplayToAllSAR(string.format("%s has been picked up by %s", _woundedGroupName, _currentHeli), self.coalition, self.messageTime) break end -- end name check end -- end loop if not inTransit then -- KIA - self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, _heliName) + self:_DisplayToAllSAR(string.format("%s is KIA ", _woundedGroupName), self.coalition, self.messageTime) end --stops the message being displayed again self:_RemoveNameFromDownedPilots(_woundedGroupName) - --self.woundedGroups[_woundedGroupName] = nil end --continue return inTransit @@ -1244,18 +1228,14 @@ function CSAR:_RescuePilots(_heliUnit) return end - -- TODO: count saved units? - local PilotsSaved = 0 - for _,_units in pairs(_rescuedGroups) do - PilotsSaved = PilotsSaved + 1 - end - + -- DONE: count saved units? + local PilotsSaved = self:_PilotsOnboard(_heliName) self.inTransitGroups[_heliName] = nil - local _txt = string.format("%s: The pilots have been taken to the\nmedical clinic. Good job!", _heliName) + local _txt = string.format("%s: The %d pilot(s) have been taken to the\nmedical clinic. Good job!", _heliName, PilotsSaved) - self:_DisplayMessageToSAR(_heliUnit, _txt, 10) + self:_DisplayMessageToSAR(_heliUnit, _txt, self.messageTime) -- trigger event self:__Rescued(-1,_heliUnit,_heliName, PilotsSaved) end @@ -1276,15 +1256,26 @@ end --- Display message to single Unit. -- @param #CSAR self --- @param Wrapper.Unit#UNIT _unit --- @param #string _text --- @param #number _time --- @param #boolean _clear -function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear) +-- @param Wrapper.Unit#UNIT _unit Unit #UNIT to display to. +-- @param #string _text Text of message. +-- @param #number _time Message show duration. +-- @param #boolean _clear (optional) Clear screen. +-- @param #boolean _speak (optional) Speak message via SRS. +function CSAR:_DisplayMessageToSAR(_unit, _text, _time, _clear, _speak) self:T(self.lid .. " _DisplayMessageToSAR") local group = _unit:GetGroup() local _clear = _clear or nil + local _time = _time or self.messageTime local m = MESSAGE:New(_text,_time,"Info",_clear):ToGroup(group) + -- integrate SRS + if _speak and self.useSRS then + local srstext = SOUNDTEXT:New(_text) + local path = self.SRSPath + local modulation = self.SRSModulation + local channel = self.SRSchannel + local msrs = MSRS:New(path,channel,modulation) + msrs:PlaySoundText(srstext, 2) + end end --- Function to get string of a group's position. @@ -1341,7 +1332,14 @@ function CSAR:_DisplayActiveSAR(_unitName) local _woundcoord = _woundedGroup:GetCoordinate() local _distance = self:_GetDistance(_helicoord, _woundcoord) self:T({_distance = _distance}) - table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %.3fKM ", _value.desc, _coordinatesText, _value.frequency / 1000, _distance / 1000.0) }) + -- change distance to miles if self.coordtype < 4 + local distancetext = "" + if self.coordtype < 4 then + distancetext = string.format("%.3fnm",UTILS.MetersToNM(_distance)) + else + distancetext = string.format("%.3fkm", _distance/1000.0) + end + table.insert(_csarList, { dist = _distance, msg = string.format("%s at %s - %.2f KHz ADF - %s ", _value.desc, _coordinatesText, _value.frequency / 1000, distancetext) }) end end @@ -1355,7 +1353,7 @@ function CSAR:_DisplayActiveSAR(_unitName) _msg = _msg .. "\n" .. _line.msg end - self:_DisplayMessageToSAR(_heli, _msg, 20) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime*2) end --- Find the closest downed pilot to a heli. @@ -1374,7 +1372,6 @@ function CSAR:_GetClosestDownedPilot(_heli) local DownedPilotsTable = self.downedPilots for _, _groupInfo in pairs(DownedPilotsTable) do local _woundedName = _groupInfo.name - --local _tempWounded = GROUP:FindByName(_woundedName) local _tempWounded = _groupInfo.group -- check group exists and not moving to someone else @@ -1408,29 +1405,34 @@ function CSAR:_SignalFlare(_unitName) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - - local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Signal Flare at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) - self:_DisplayMessageToSAR(_heli, _msg, 20) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal flare at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) end end --- Display info to all SAR groups. -- @param #CSAR self --- @param #string _message --- @param #number _side --- @param #string _ignore -function CSAR:_DisplayToAllSAR(_message, _side, _ignore) +-- @param #string _message Message to display. +-- @param #number _side Coalition of message. +-- @param #number _messagetime How long to show. +function CSAR:_DisplayToAllSAR(_message, _side, _messagetime) self:T(self.lid .. " _DisplayToAllSAR") for _, _unitName in pairs(self.csarUnits) do local _unit = self:_GetSARHeli(_unitName) if _unit then - if not _ignore then - self:_DisplayMessageToSAR(_unit, _message, 10) + if not _messagetime then + self:_DisplayMessageToSAR(_unit, _message, _messagetime) end end end @@ -1448,13 +1450,19 @@ function CSAR:_Reqsmoke( _unitName ) local _closest = self:_GetClosestDownedPilot(_heli) if _closest ~= nil and _closest.pilot ~= nil and _closest.distance < 8000.0 then local _clockDir = self:_GetClockDirection(_heli, _closest.pilot) - local _msg = string.format("%s - %.2f KHz ADF - %.3fM - Popping Blue smoke at your %s o\'clock", _closest.groupInfo.desc, _closest.groupInfo.frequency / 1000, _closest.distance, _clockDir) - self:_DisplayMessageToSAR(_heli, _msg, 20) + local _distance = 0 + if self.coordtype < 4 then + _distance = string.format("%.3fnm",UTILS.MetersToNM(_closest.distance)) + else + _distance = string.format("%.3fkm",_closest.distance) + end + local _msg = string.format("%s - Popping signal smoke at your %s o\'clock. Distance %s", _unitName, _clockDir, _distance) + self:_DisplayMessageToSAR(_heli, _msg, self.messageTime, false, true) local _coord = _closest.pilot:GetCoordinate() local color = self.smokecolor _coord:Smoke(color) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8KM", 20) + self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) end end @@ -1493,7 +1501,6 @@ function CSAR:_GetClosestMASH(_heli) if self.allowFARPRescue then local position = _heli:GetCoordinate() local afb,distance = position:GetClosestAirbase2(nil,self.coalition) - --local distance = GetCloseAirbase(position,self.coalition,nil) _shortestDistance = distance end @@ -1532,7 +1539,7 @@ function CSAR:_CheckOnboard(_unitName) for _, _onboard in pairs(self.inTransitGroups[_unitName]) do _text = _text .. "\n" .. _onboard.desc end - self:_DisplayMessageToSAR(_unit, _text, self.messageTime) + self:_DisplayMessageToSAR(_unit, _text, self.messageTime*2) end end @@ -1552,10 +1559,7 @@ function CSAR:_AddMedevacMenuItem() if _unit then if _unit:IsAlive() then local unitName = _unit:GetName() - --if not self.csarUnits[unitName] then - --self.csarUnits[unitName] = unitName _UnitList[unitName] = unitName - --end end -- end isAlive end -- end if _unit end -- end for @@ -1700,9 +1704,7 @@ function CSAR:_GetClockDirection(_heli, _group) if _heading then local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end - --clock = math.abs(math.floor(Aspect / 30)) clock = math.floor(Aspect / 30) - --clock = UTILS.Round(clock,-2) end return clock end @@ -1730,7 +1732,6 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local Frequency = _freq -- Freq in Hertz local Sound = "l10n/DEFAULT/"..self.radioSound trigger.action.radioTransmission(Sound, _radioUnit:GetPositionVec3(), 0, false, Frequency, 1000) -- Beacon in MP only runs for exactly 30secs straight - --timer.scheduleFunction(self._RefreshRadioBeacons, { _group, _freq }, timer.getTime() + 30) end end @@ -1908,7 +1909,8 @@ end -- @param #string To To state. -- @param Wrapper.Unit#UNIT HeliUnit Unit of the helicopter. -- @param #string HeliName Name of the helicopter group. -function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName) +-- @param #number PilotsSaved Number of the saved pilots on board when landing. +function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 return self @@ -1930,4 +1932,3 @@ end -------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End Ops.CSAR -------------------------------------------------------------------------------------------------------------------------------------------------------------------- - From 270c69344f5fff637fcec68a755e4908aaf30311 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 07:42:45 +0200 Subject: [PATCH 09/11] Update CSAR.lua Updated documentation / clarification. Added feature docu on using SRS --- Moose Development/Moose/Ops/CSAR.lua | 40 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index f95c319da..55ac59b82 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -64,27 +64,37 @@ -- -- ## 2. Options -- --- The following options are available (with their defaults): +-- The following options are available (with their defaults). Only set the ones you want changed: -- -- self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined Arms. --- self.allowFARPRescue = true -- allows pilot to be rescued by landing at a FARP or Airbase. Else MASH only. --- self.autosmoke = false -- automatically smoke downed pilot location when a heli is near. +-- self.allowFARPRescue = true -- allows pilots to be rescued by landing at a FARP or Airbase. Else MASH only! +-- self.autosmoke = false -- automatically smoke a downed pilot's location when a heli is near. -- self.coordtype = 1 -- Use Lat/Long DDM (0), Lat/Long DMS (1), MGRS (2), Bullseye imperial (3) or Bullseye metric (4) for coordinates. --- self.csarOncrash = true -- If set to true, will generate a downed pilot when a plane crashes as well. --- self.csarPrefix = { "helicargo", "MEDEVAC"} -- prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! --- self.enableForAI = true -- set to false to disable AI units from being rescued. --- self.extractDistance = 500 -- Distance the Downed pilot will run to the rescue helicopter. +-- self.csarOncrash = true -- (WIP) If set to true, will generate a downed pilot when a plane crashes as well. +-- self.enableForAI = true -- set to false to disable AI pilots from being rescued. +-- self.pilotRuntoExtractPoint = true -- Downed pilot will run to the rescue helicopter up to self.extractDistance in meters. +-- self.extractDistance = 500 -- Distance the downed pilot will start to run to the rescue helicopter. -- self.immortalcrew = true -- Set to true to make wounded crew immortal. -- self.invisiblecrew = false -- Set to true to make wounded crew insvisible. -- self.loadDistance = 75 -- configure distance for pilots to get into helicopter in meters. -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. --- self.messageTime = 10 -- Time to show messages for in seconds. Doubled for long messages. --- self.pilotRuntoExtractPoint = true -- Downed Pilot will run to the rescue helicopter up to self.extractDistance in meters. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the Pilot radio beacons. --- self.smokecolor = 4 -- Color of smokemarker for blue side, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. --- self.template = Template or "generic" -- late activated template for downed pilot, usually single infantry soldier. --- self.useprefix = true -- Use the Prefixed defined below, Requires Unit have the Prefix defined below. +-- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. +-- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. +-- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! +-- self.verbose = 0 -- set to > 1 for stats output for debugging. +-- +-- ## 2.1 Experimental Features +-- +-- WARNING - Here'll be dragons! +-- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua +-- Needs SRS => 1.9.6 to work (works on the *server* side of SRS) +-- self.useSRS = false -- Set true to use FF's SRS integration +-- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) +-- self.SRSchannel = 300 -- radio channel +-- self.SRSModulation = radio.modulation.AM -- modulation -- -- ## 3. Events -- @@ -142,7 +152,7 @@ -- @field #CSAR CSAR = { ClassName = "CSAR", - verbose = 1, + verbose = 0, lid = "", coalition = 1, coalitiontxt = "blue", @@ -212,7 +222,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.2r1" +CSAR.version="0.1.2r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list From 64262d6eccd6cda8bdc1eed8a9b2b11545046f57 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 10:32:52 +0200 Subject: [PATCH 10/11] Update CSAR.lua (#1550) --- Moose Development/Moose/Ops/CSAR.lua | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 55ac59b82..a915d984c 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -80,7 +80,7 @@ -- self.mashprefix = {"MASH"} -- prefixes of #GROUP objects used as MASHes. -- self.max_units = 6 -- number of pilots that can be carried if #CSAR.AircraftType is undefined. -- self.messageTime = 15 -- Time to show messages for in seconds. Doubled for long messages. --- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots' radio beacons. +-- self.radioSound = "beacon.ogg" -- the name of the sound file to use for the pilots\' radio beacons. -- self.smokecolor = 4 -- Color of smokemarker, 0 is green, 1 is red, 2 is white, 3 is orange and 4 is blue. -- self.useprefix = true -- Requires CSAR helicopter #GROUP names to have the prefix(es) defined below. -- self.csarPrefix = { "helicargo", "MEDEVAC"} -- #GROUP name prefixes used for useprefix=true - DO NOT use # in helicopter names in the Mission Editor! @@ -88,10 +88,10 @@ -- -- ## 2.1 Experimental Features -- --- WARNING - Here'll be dragons! +-- "WARNING - Here\'ll be dragons! -- DANGER - For this to work you need to de-sanitize your mission environment (all three entries) in \Scripts\MissionScripting.lua --- Needs SRS => 1.9.6 to work (works on the *server* side of SRS) --- self.useSRS = false -- Set true to use FF's SRS integration +-- Needs SRS => 1.9.6 to work (works on the *server* side of SRS)" +-- self.useSRS = false -- Set true to use FF\'s SRS integration -- self.SRSPath = "E:\\Program Files\\DCS-SimpleRadio-Standalone\\" -- adjust your own path in your SRS installation -- server(!) -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation @@ -222,7 +222,7 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.2r2" +CSAR.version="0.1.3r1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -262,6 +262,7 @@ function CSAR:New(Coalition, Template, Alias) end else self.coalition = Coalition + self.coalitiontxt = string.lower(UTILS.GetCoalitionName(self.coalition)) end -- Set alias. @@ -1082,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance * self.loadtimemax ) / self.extractDistance ) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 1.94 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) @@ -1427,7 +1428,11 @@ function CSAR:_SignalFlare(_unitName) local _coord = _closest.pilot:GetCoordinate() _coord:FlareRed(_clockDir) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end end @@ -1472,7 +1477,11 @@ function CSAR:_Reqsmoke( _unitName ) local color = self.smokecolor _coord:Smoke(color) else - self:_DisplayMessageToSAR(_heli, "No Pilots within 8km/4.32nm", self.messageTime) + local disttext = "4.3nm" + if self.coordtype == 4 then + disttext = "8km" + end + self:_DisplayMessageToSAR(_heli, string.format("No Pilots within %s",disttext), self.messageTime) end end @@ -1715,6 +1724,7 @@ function CSAR:_GetClockDirection(_heli, _group) local Aspect = Angle - _heading if Aspect == 0 then Aspect = 360 end clock = math.floor(Aspect / 30) + if clock == 0 then clock = 12 end end return clock end From cd1935be1d939a780e9f5d16dd6e1ed359b4ef66 Mon Sep 17 00:00:00 2001 From: Applevangelist <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 15 Jun 2021 15:49:58 +0200 Subject: [PATCH 11/11] Update CSAR.lua (#1551) --- Moose Development/Moose/Ops/CSAR.lua | 63 +++++++++++++++------------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index a915d984c..c7600e156 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -96,12 +96,19 @@ -- self.SRSchannel = 300 -- radio channel -- self.SRSModulation = radio.modulation.AM -- modulation -- --- ## 3. Events +-- ## 3. Results +-- +-- Number of successful landings with save pilots and aggregated number of saved pilots is stored in these variables in the object: +-- +-- self.rescues -- number of successful landings *with* saved pilots +-- self.rescuedpilots -- aggregated number of pilots rescued from the field (of *all* players) +-- +-- ## 4. Events -- -- The class comes with a number of FSM-based events that missions designers can use to shape their mission. -- These are: -- --- ### 1. PilotDown. +-- ### 4.1. PilotDown. -- -- The event is triggered when a new downed pilot is detected. Use e.g. `function my_csar:OnAfterPilotDown(...)` to link into this event: -- @@ -109,7 +116,7 @@ -- ... your code here ... -- end -- --- ### 2. Approach. +-- ### 4.2. Approach. -- -- A CSAR helicpoter is closing in on a downed pilot. Use e.g. `function my_csar:OnAfterApproach(...)` to link into this event: -- @@ -117,7 +124,7 @@ -- ... your code here ... -- end -- --- ### 3. Boarded. +-- ### 4.3. Boarded. -- -- The pilot has been boarded to the helicopter. Use e.g. `function my_csar:OnAfterBoarded(...)` to link into this event: -- @@ -125,7 +132,7 @@ -- ... your code here ... -- end -- --- ### 4. Returning. +-- ### 4.4. Returning. -- -- The CSAR helicopter is ready to return to an Airbase, FARP or MASH. Use e.g. `function my_csar:OnAfterReturning(...)` to link into this event: -- @@ -133,7 +140,7 @@ -- ... your code here ... -- end -- --- ### 5. Rescued. +-- ### 4.5. Rescued. -- -- The CSAR helicopter has landed close to an Airbase/MASH/FARP and the pilots are safe. Use e.g. `function my_csar:OnAfterRescued(...)` to link into this event: -- @@ -141,7 +148,7 @@ -- ... your code here ... -- end -- --- ## 4. Spawn downed pilots at location to be picked up. +-- ## 5. Spawn downed pilots at location to be picked up. -- -- If missions designers want to spawn downed pilots into the field, e.g. at mission begin to give the helicopter guys works, they can do this like so: -- @@ -179,6 +186,7 @@ CSAR = { bluemash = {}, smokecolor = 4, rescues = 0, + rescuedpilots = 0, } --- Downed pilots info. @@ -222,14 +230,13 @@ CSAR.AircraftType["Mi-24"] = 8 --- CSAR class version. -- @field #string version -CSAR.version="0.1.3r1" +CSAR.version="0.1.3r2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: SRS Integration (to be tested) --- WONTDO: Slot blocker etc +-- DONE: SRS Integration (to be tested) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -316,6 +323,7 @@ function CSAR:New(Coalition, Template, Alias) -- settings, counters etc self.rescues = 0 -- counter for successful rescue landings at FARP/AFB/MASH + self.rescuedpilots = 0 -- counter for saved pilots self.csarOncrash = true -- If set to true, will generate a csar when a plane crashes as well. self.allowDownedPilotCAcontrol = false -- Set to false if you don't want to allow control by Combined arms. self.enableForAI = true -- set to false to disable AI units from being rescued. @@ -692,19 +700,11 @@ function CSAR:_EventHandler(EventData) self.takenOff[_event.IniPlayerName] = nil end - -- if its a sar heli, re-add check status script - for _, _heliName in pairs(self.csarUnits) do - if _heliName == _event.IniPlayerName then - -- add back the status script - local DownedPilotTable = self.downedPilots - for _, _groupInfo in pairs(DownedPilotTable) do -- #CSAR.DownedPilot - if _groupInfo.side == _event.IniCoalition then - local _woundedName = _groupInfo.name - self:_CheckWoundedGroupStatus(_heliName,_woundedName) - end - end - end - end + local _unit = _event.IniUnit + local _group = _event.IniGroup + if _unit:IsHelicopter() or _group:IsHelicopter() then + self:_AddMedevacMenuItem() + end return true @@ -1083,7 +1083,7 @@ function CSAR:_CheckCloseWoundedGroup(_distance, _heliUnit, _heliName, _woundedG if (_distance < self.extractDistance) then local _time = self.landedStatus[_lookupKeyHeli] if _time == nil then - self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 1.94 ) + self.landedStatus[_lookupKeyHeli] = math.floor( (_distance - self.loadDistance) / 3.6 ) _time = self.landedStatus[_lookupKeyHeli] self:_OrderGroupToMoveToPoint(_woundedGroup, _heliUnit:GetCoordinate()) self:_DisplayMessageToSAR(_heliUnit, "Wait till " .. _pilotName .. " gets in. \nETA " .. _time .. " more seconds.", self.messageTime, true) @@ -1789,7 +1789,7 @@ function CSAR:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Ejection, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterAircraft, self._EventHandler) self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) - self:HandleEvent(EVENTS.Dead, self._EventHandler) + self:HandleEvent(EVENTS.PilotDead, self._EventHandler) self:_GenerateVHFrequencies() if self.useprefix then local prefixes = self.csarPrefix or {} @@ -1855,10 +1855,14 @@ function CSAR:onafterStatus(From, Event, To) end if self.verbose > 0 then - local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Rescue (landings): %d",self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues) + local text = string.format("%s Active SAR: %d | Downed Pilots in field: %d | Pilots boarded: %d | Landings: %d | Pilots rescued: %d", + self.lid,NumberOfSARPilots,PilotsInFieldN,PilotsBoarded,self.rescues,self.rescuedpilots) self:T(text) - if self.verbose > 1 then - local m = MESSAGE:New(text,"10","Status"):ToAll() + if self.verbose < 2 then + self:I(text) + elseif self.verbose > 1 then + self:I(text) + local m = MESSAGE:New(text,"10","Status",true):ToCoalition(self.coalition) end end self:__Status(-20) @@ -1878,7 +1882,7 @@ function CSAR:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.PlayerEnterUnit) self:UnHandleEvent(EVENTS.PlayerEnterAircraft) - self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.PilotDead) self:T(self.lid .. "Stopped.") return self end @@ -1933,6 +1937,7 @@ end function CSAR:onbeforeRescued(From, Event, To, HeliUnit, HeliName, PilotsSaved) self:T({From, Event, To, HeliName, HeliUnit}) self.rescues = self.rescues + 1 + self.rescuedpilots = self.rescuedpilots + PilotsSaved return self end