From 51a1f5601130a9fcaebad9bede1de75ff95de20b Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 20 Nov 2018 00:15:39 +0100 Subject: [PATCH] AIRBOSS v0.3.0 RESCUEHELO v0.9.2 RECOVERYTANKER v0.9.2 --- Moose Development/Moose/AI/AI_Formation.lua | 2 +- Moose Development/Moose/Core/Point.lua | 2 +- .../Moose/Functional/Warehouse.lua | 5 +- Moose Development/Moose/Ops/Airboss.lua | 205 ++++++++---- .../Moose/Ops/RecoveryTanker.lua | 293 ++++++++++++++---- Moose Development/Moose/Ops/RescueHelo.lua | 267 ++++++++++++---- 6 files changed, 593 insertions(+), 181 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index c6f597ca8..c02096609 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -906,7 +906,7 @@ function AI_FORMATION:SetFlightRandomization( FlightRandomization ) --R2.1 end ---- Follow event fuction. Check if coming from state "stopped". If so the transition is rejected. +--- Stop function. Formation will not be updated any more. -- @param #AI_FORMATION self -- @param Core.Set#SET_GROUP FollowGroupSet The following set of groups. -- @param #string From From state. diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8a6e0ffda..b79174989 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1132,7 +1132,7 @@ do -- COORDINATE -- HeliGroup:Route( { LandWaypoint }, 1 ) -- Start landing the helicopter in one second. -- function COORDINATE:WaypointAirLanding( Speed, airbase, DCSTasks, description ) - return self:WaypointAir( nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, airbase, DCSTasks, description ) + return self:WaypointAir(nil, COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, Speed, nil, airbase, DCSTasks, description) end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 55ee5e9d8..556d93afd 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1790,7 +1790,7 @@ WAREHOUSE.version="0.6.6" --- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure. -- @param #WAREHOUSE self --- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. +-- @param Wrapper.Static#STATIC warehouse The physical structure representing the warehouse. -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) @@ -1798,8 +1798,9 @@ function WAREHOUSE:New(warehouse, alias) -- Check if just a string was given and convert to static. if type(warehouse)=="string" then - warehouse=GROUP:FindByName(warehouse) + warehouse=UNIT:FindByName(warehouse) if warehouse==nil then + env.info(string.format("FF no warehouse unit with name %s found trying static.", warehouse)) warehouse=STATIC:FindByName(warehouse, true) end end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index f33c9bc13..38b52fd46 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -10,11 +10,11 @@ -- * Different skill levels from tipps on-the-fly for students to complete ziplip for pros. -- * Rescue helo option. -- * Recovery tanker option. --- * Voice overs for LSO and AIRBOSS calls. Can easily customized by users. +-- * Voice overs for LSO and AIRBOSS calls. Can easily be customized by users. -- * Automatic TACAN and ICLS channel setting. -- * Different radio channels for LSO and airboss calls. -- * F10 radio menu including carrier info (weather, radio frequencies, TACAN/ICLS channels, LSO grades). --- * Multiple carriers supported. +-- * Multiple carriers supported (due to object oriented approach). -- -- **PLEASE NOTE** that his class is work in progress and in an **alpha** stage. -- At the moment training parameters are optimized for F/A-18C Hornet as aircraft and USS John C. Stennis as carrier. @@ -68,6 +68,7 @@ -- @field #table Qpattern Queue of aircraft groups in the landing pattern. -- @field Ops.RescueHelo#RESCUEHELO rescuehelo Rescue helo flying in close formation with the carrier. -- @field Ops.RecoveryTanker#RECOVERYTANKER tanker Recovery tanker flying overhead of carrier. +-- @field Functional.Warehouse#WAREHOUSE warehouse Warehouse object of the carrier. -- @field #table recoverytime List of time intervals when aircraft are recovered. -- @extends Core.Fsm#FSM @@ -123,6 +124,7 @@ AIRBOSS = { Qmarshal = {}, rescuehelo = nil, tanker = nil, + warehouse = nil, recoverytime = {}, } @@ -137,6 +139,8 @@ AIRBOSS.AircraftPlayer={ --- Aircraft types capable of landing on carrier (human+AI). -- @type AIRBOSS.AircraftCarrier +-- @field #string AV8B AV-8B Night Harrier. +-- @field #string HORNET F/A-18C Lot 20 Hornet. -- @field #string S3B Lockheed S-3B Viking. -- @field #string S3BTANKER Lockheed S-3B Viking tanker. -- @field #string E2D Grumman E-2D Hawkeye AWACS. @@ -150,7 +154,7 @@ AIRBOSS.AircraftCarrier={ E2D="E-2C", FA18C="F/A-18C", F14A="F-14A", - --TODO: Add A-A4-E-C + --TODO: Add A4-E-C } @@ -417,12 +421,13 @@ AIRBOSS.MenuF10={} --- Airboss class version. -- @field #string version -AIRBOSS.version="0.2.9" +AIRBOSS.version="0.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Set case II and III times. -- TODO: Get an _OK_ pass if long in groove. Possible other pattern wave offs as well?! -- TODO: Add radio transmission queue for LSO and airboss. -- TODO: Get correct wire when trapped. @@ -459,7 +464,10 @@ AIRBOSS.version="0.2.9" function AIRBOSS:New(carriername, alias) -- Inherit everthing from FSM class. - local self = BASE:Inherit(self, FSM:New()) -- #AIRBOSS + local self=BASE:Inherit(self, FSM:New()) -- #AIRBOSS + + -- Debug. + self:F2({carriername=carriername, alias=alias}) -- Set carrier unit. self.carrier=UNIT:FindByName(carriername) @@ -515,8 +523,8 @@ function AIRBOSS:New(carriername, alias) return nil end - -- Zone 3 NM astern and 100 m starboard of the carrier with radius of 2.0 km. - self.zoneInitial=ZONE_UNIT:New("Initial Zone", self.carrier, 2.0*1000, {dx=-UTILS.NMToMeters(3), dy=100, relative_to_unit=true}) + -- Zone 3 NM astern and 100 m starboard of the carrier with radius of 0.5 km. + self.zoneInitial=ZONE_UNIT:New("Initial Zone", self.carrier, 0.5*1000, {dx=-UTILS.NMToMeters(3), dy=100, relative_to_unit=true}) -- CCA 50 NM radius zone around the carrier. self:SetCarrierControlledArea() @@ -729,6 +737,35 @@ function AIRBOSS:SetCarrierradio(frequency, modulation) end +--- Define rescue helicopter associated with the carrier. +-- @param #AIRBOSS self +-- @param Ops.RescueHelo#RESCUEHELO rescuehelo Rescue helo object. +-- @return #ARIBOSS self +function AIRBOSS:SetRescueHelo(rescuehelo) + self.rescuehelo=rescuehelo + return self +end + +--- Define recovery tanker associated with the carrier. +-- @param #AIRBOSS self +-- @param Ops.RecoveryTanker#RECOVERYTANKER recoverytanker Recovery tanker object. +-- @return #ARIBOSS self +function AIRBOSS:SetRecoveryTanker(recoverytanker) + self.tanker=recoverytanker + return self +end + + +--- Define warehouse associated with the carrier. +-- @param #AIRBOSS self +-- @param Functional.Warehouse#WAREHOUSE warehouse Warehouse object of the carrier. +-- @return #ARIBOSS self +function AIRBOSS:SetWarehouse(warehouse) + self.warehouse=warehouse + return self +end + + --- Check if carrier is recovering aircraft. -- @param #AIRBOSS self -- @return #boolean If true, time slot for recovery is open. @@ -805,8 +842,8 @@ function AIRBOSS:onafterStatus(From, Event, To) -- Update marshal and pattern queue every 30 seconds. if time-self.Tqueue>30 then - local text=string.format("AIRBOSS %s: Status %s.", self.alias, self:GetState()) - self:I(text) + local text=string.format("Status %s.", self:GetState()) + self:I(self.lid..text) -- Scan carrier zone for new aircraft. self:_ScanCarrierZone() @@ -946,14 +983,15 @@ function AIRBOSS:_InitStennis() -- 4k descent from holding pattern to 5k platform self.C3Descent4k.name="4k Descent" - self.C3Descent4k.Xmin=-UTILS.NMToMeters(35) + self.C3Descent4k.Xmin=-UTILS.NMToMeters(50) self.C3Descent4k.Xmax=-UTILS.NMToMeters(20) - self.C3Descent4k.Zmin=-UTILS.NMToMeters(30) - self.C3Descent4k.Zmax= UTILS.NMToMeters(30) + self.C3Descent4k.Zmin=-UTILS.NMToMeters(10) + self.C3Descent4k.Zmax= UTILS.NMToMeters(3) self.C3Descent4k.LimitXmin=nil self.C3Descent4k.LimitXmax=-UTILS.NMToMeters(20) --TODO: better rho dist. decrease descent 20 2000 ft/min at 5000 ft alt and user rad alt. self.C3Descent4k.LimitZmin=nil self.C3Descent4k.LimitZmax=nil + -- TODO: alt, AoA are more aircraft functions rather than carrier self.C3Descent4k.Altitude=nil --UTILS.FeetToMeters(5000) self.C3Descent4k.AoA=nil self.C3Descent4k.Distance=nil @@ -1004,7 +1042,7 @@ function AIRBOSS:_InitStennis() self.Upwind.name="Upwind" self.Upwind.Xmin=-UTILS.NMToMeters(4) self.Upwind.Xmax=nil - self.Upwind.Zmin=0 + self.Upwind.Zmin=-100 self.Upwind.Zmax=1000 self.Upwind.LimitXmin=0 self.Upwind.LimitXmax=nil @@ -1018,8 +1056,8 @@ function AIRBOSS:_InitStennis() self.BreakEarly.name="Early Break" self.BreakEarly.Xmin=-500 self.BreakEarly.Xmax=UTILS.NMToMeters(5) - self.BreakEarly.Zmin=-3700 - self.BreakEarly.Zmax=1500 + self.BreakEarly.Zmin=-UTILS.NMToMeters(2) + self.BreakEarly.Zmax=UTILS.NMToMeters(1) self.BreakEarly.LimitXmin=0 self.BreakEarly.LimitXmax=nil self.BreakEarly.LimitZmin=-370 -- 0.2 NM port of carrier @@ -1032,8 +1070,8 @@ function AIRBOSS:_InitStennis() self.BreakLate.name="Late Break" self.BreakLate.Xmin=-500 self.BreakLate.Xmax=UTILS.NMToMeters(5) - self.BreakLate.Zmin=-3700 - self.BreakLate.Zmax=1500 + self.BreakLate.Zmin=-UTILS.NMToMeters(2) + self.BreakLate.Zmax=UTILS.NMToMeters(1) self.BreakLate.LimitXmin=0 self.BreakLate.LimitXmax=nil self.BreakLate.LimitZmin=-1470 --0.8 NM @@ -1156,6 +1194,8 @@ function AIRBOSS:_CheckQueue() local TpatternMin=120 if self.case==1 then TpatternMin=45 + else + TpatternMin=120 end -- Min time in marshal before send to landing pattern. @@ -1416,6 +1456,12 @@ function AIRBOSS:_MarshalAI(flight) -- Flight group name. local group=flight.group local groupname=flight.groupname + + -- Check that we do not add a recovery tanker for marshaling. + -- TODO: Fix group name. + if self.tanker and self.tanker.tanker:GetName()==groupname then + return + end -- Number of already full marshal stacks. local nstacks=#self.Qmarshal @@ -1499,7 +1545,7 @@ function AIRBOSS:_GetMarshalAltitude(stack) Dist=UTILS.NMToMeters(2.5) p1=Carrier:Translate(Dist, hdg-70) else - -- CASE III: Holding at 6000 ft on a racetrack pattern astern the carrier. + -- CASE II/III: Holding at 6000 ft on a racetrack pattern astern the carrier. angels0=6 Dist=UTILS.NMToMeters((stack-1)*angels0+15) p1=Carrier:Translate(-Dist, hdg) @@ -1688,10 +1734,8 @@ function AIRBOSS:_CheckPlayerStatus() -- Status undefined. local time=timer.getAbsTime() local clock=UTILS.SecondsToClock(time) - self:I(string.format("Player status undefined. Waiting for next step. Time %s", clock)) + self:T3(string.format("Player status undefined. Waiting for next step. Time %s", clock)) - -- Jump directly to CASE I straight in approach. - --playerData.step=AIRBOSS.PatternStep.COMMENCING -- Jump to final/groove for testing. if self.groovedebug then @@ -1853,7 +1897,7 @@ function AIRBOSS:OnEventBirth(EventData) --self:RadioTransmission(self.LSOradio, self.radiocall.LONGINGROOVE, false, 20) -- Start in the groove for debugging. - self.groovedebug=false + self.groovedebug=true end end @@ -1887,6 +1931,7 @@ function AIRBOSS:OnEventLand(EventData) self:T3(self.lid.."LAND: player = "..tostring(_playername)) if _unit and _playername then + -- Human Player landed. local _uid=_unit:GetID() local _group=_unit:GetGroup() @@ -1935,19 +1980,19 @@ function AIRBOSS:OnEventLand(EventData) end else - - -- TODO: Get landing coodinates of AI hornet for perfect _OK_ 3-wire pass! - -- Coordinate at landing event - local coord=EventData.IniUnit:GetCoordinate() - - -- Debug mark of player landing coord. - local dist=coord:Get2DDistance(self:GetCoordinate()) - - local text=string.format("AI landing dist=%.1f m", dist) - env.info(text) + -- AI unit landed. + + -- Coordinate at landing event + local coord=EventData.IniUnit:GetCoordinate() + + -- Debug mark of player landing coord. + local dist=coord:Get2DDistance(self:GetCoordinate()) + + local text=string.format("AI landing dist=%.1f m", dist) + env.info(text) - local lp=coord:MarkToAll(text) - coord:SmokeGreen() + local lp=coord:MarkToAll(text) + coord:SmokeGreen() -- AI: Decrease number of units in flight and remove group from pattern queue if all units landed. if self:_InQueue(self.Qpattern, EventData.IniGroup) then @@ -1974,10 +2019,11 @@ function AIRBOSS:OnEventCrash(EventData) -- TODO: Update queues! + -- TODO: decrease number of units in group if _unit and _playername then self:I(self.lid.."Player %s crashed!",_playername) else - self:I(self.lid.."AI unit %s crashed!", EventData.IniUnitName) + self:I(self.lid.."AI unit %s crashed!", EventData.IniUnitName) end end @@ -2056,16 +2102,25 @@ function AIRBOSS:_InitPlayer(playerData) return playerData end +local _bla=true + --- Holding. -- @param #AIRBOSS self -- @param #AIRBOSS.PlayerData playerData Player data. function AIRBOSS:_Holding(playerData) + -- Player unit and flight. local unit=playerData.unit local flight=self:_GetFlightFromGroupInQueue(playerData.group, self.flights) + + -- Current stack. local stack=flight.flag:Get() - local alt, c1, c2=self:_GetMarshalAltitude(stack) + -- Pattern alitude. + local patternalt, c1, c2=self:_GetMarshalAltitude(stack) + + -- Player altitude. + local playeralt=unit:GetAltitude() -- Create a holding zone depending on recovery case. local zoneHolding --Core.Zone#ZONE @@ -2082,52 +2137,87 @@ function AIRBOSS:_Holding(playerData) -- Create an array of a square! local p={} - p[1]=c1:GetVec2() - p[2]=c2:GetVec2() - p[3]=c2:Translate(UTILS.NMToMeters(5), hdg-90):GetVec2() - p[4]=c1:Translate(UTILS.NMToMeters(5), hdg-90):GetVec2() + p[1]=c1:Translate(UTILS.NMToMeters(1), hdg+90):GetVec2() --c1 is at (angels+15) NM directly behind the carrier. We translate it 1 NM starboard. + p[2]=c2:Translate(UTILS.NMToMeters(1), hdg+90):GetVec2() --c2 is 10 NM further behind. Also translated 1 NM starboard. + p[3]=c2:Translate(UTILS.NMToMeters(7), hdg-90):GetVec2() --p3 6 NM port of carrier. + p[4]=c1:Translate(UTILS.NMToMeters(7), hdg-90):GetVec2() --p4 6 NM port of carrier. + -- Square zone length=10NM width=6 NM behind the carrier starting at angels+15 NM behind the carrier. + -- So stay 0-5 NM (+1 NM error margin) port of carrier. zoneHolding=ZONE_POLYGON_BASE:New("CASE II/III Holding Zone", p) end - --if bla then - -- zoneHolding:SmokeZone(SMOKECOLOR.Green) - -- bla=false - --end + if _bla then + zoneHolding:SmokeZone(SMOKECOLOR.Green) + _bla=false + end -- Check if player is in holding zone. - local inholdingzone=unit:IsInZone(zoneHolding) + local inholdingzone=unit:IsInZone(zoneHolding) + + -- Check player alt is +-500 feet of assigned pattern alt. + local altdiff=playeralt-patternalt + local goodalt=math.abs(altdiff)90 and self:_CheckLimits(X, Z, self.Wake) then -- Message to player. self:_SendMessageToPlayer("You are already at the wake and have not passed the 90! Turn faster next time!", 10, playerData) + --TODO: pattern WO? end end @@ -4030,9 +4128,9 @@ function AIRBOSS:_AddF10Commands(_unitName) -- F10/Airboss//Kneeboard missionCommands.addCommandForGroup(_gid, "Attitude Monitor ON/OFF", _kneeboardPath, self._AttitudeMonitor, self, playername) - missionCommands.addCommandForGroup(_gid, "Weather Report", _kneeboardPath, self._DisplayCarrierWeather, self, _unitName) - missionCommands.addCommandForGroup(_gid, "Carrier Info", _kneeboardPath, self._DisplayCarrierInfo, self, _unitName) - + missionCommands.addCommandForGroup(_gid, "Weather Report", _kneeboardPath, self._DisplayCarrierWeather, self, _unitName) + missionCommands.addCommandForGroup(_gid, "Carrier Info", _kneeboardPath, self._DisplayCarrierInfo, self, _unitName) + -- F10/Airboss// missionCommands.addCommandForGroup(_gid, "Request Marshal?", _rootPath, self._RequestMarshal, self, _unitName) missionCommands.addCommandForGroup(_gid, "Commencing!", _rootPath, self._RequestStraightIn, self, _unitName) @@ -4063,6 +4161,8 @@ function AIRBOSS:_RequestStraightIn(_unitName) local playerData=self.players[_playername] --#AIRBOSS.PlayerData if playerData then + -- TODO: check if landing pattern is full. If so, display message "AIRBOSS: "Pattern is full." and deny step! + -- TODO: check if in marshal stack and flag is 0. If not, give message "AIRBOSS: It's not your turn yet!" and deny step! playerData.step=AIRBOSS.PatternStep.COMMENCING end end @@ -4338,4 +4438,3 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ - diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index 80f73f555..58518093d 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -5,9 +5,8 @@ -- Features: -- -- * Regular pattern update with respect to carrier positon. --- * Automatic respawning when tanker runs out of fuel. --- * Tanker can be spawned cold or hot on the carrier or any other airbase or directly in air. --- * Tanker can operate 24/7. +-- * Automatic respawning when tanker runs out of fuel for 24/7 operations. +-- * Tanker can be spawned cold or hot on the carrier or at any other airbase or directly in air. -- -- Please not that his class is work in progress and in an **alpha** stage. -- @@ -34,6 +33,9 @@ -- @field #number Tupdate Last time the pattern was updated. -- @field #number takeoff Takeoff type (cold, hot, air). -- @field #number lowfuel Low fuel threshold in percent. +-- @field #boolean respawn If true, tanker be respawned (default). If false, no respawning will happen. +-- @field #boolean respawninair If true, tanker will always be respawned in air. This has no impact on the initial spawn setting. +-- @field #boolean uncontrolledac If true, use and uncontrolled tanker group already present in the mission. -- @extends Core.Fsm#FSM --- Recovery Tanker. @@ -62,21 +64,24 @@ RECOVERYTANKER = { Tupdate = nil, takeoff = nil, lowfuel = nil, + respawn = nil, + respawninair = nil, + uncontrolledac = nil, } --- Class version. -- @field #string version -RECOVERYTANKER.version="0.9.1" +RECOVERYTANKER.version="0.9.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Possibility to add already present/spawned aircraft, e.g. for warehouse. --- TODO: Write documenation. -- TODO: Smarter pattern update function. E.g. (small) zone around carrier. Only update position when carrier leaves zone or changes heading? --- TODO: Maybe rework pattern update implementation altogether to make it smoother. +-- TODO: Write documenation. +-- DONE: Add refueling event/state. +-- DONE: Possibility to add already present/spawned aircraft, e.g. for warehouse. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -104,7 +109,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) -- Tanker group name. self.tankergroupname=tankergroupname - -- Default parameters. + -- Init default parameters. self:SetPatternUpdateInterval() self:SetAltitude() self:SetSpeed() @@ -112,6 +117,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) self:SetTakeoffAir() self:SetLowFuelThreshold() + self:SetRespawnOnOff() ----------------------- --- FSM Transitions --- @@ -123,10 +129,11 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") - self:AddTransition("Running", "RTB", "Returning") - self:AddTransition("Running", "Status", "*") - self:AddTransition("Returning", "Status", "*") - self:AddTransition("Running", "Stop", "Stopped") + self:AddTransition("*", "Refuel", "Refueling") + self:AddTransition("*", "Run", "Running") + self:AddTransition("Running", "RTB", "Returning") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "Stopped") --- Triggers the FSM event "Start" that starts the recovery tanker. Initializes parameters and starts event handlers. @@ -138,6 +145,29 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Refuel" when the tanker is refueling another aircraft. + -- @function [parent=#RECOVERYTANKER] Refuel + -- @param Wrapper.Unit#UNIT receiver Unit receiving fuel from the tanker. + -- @param #RECOVERYTANKER self + + --- Triggers delayed the FSM event "Refuel" when the tanker is refueling another aircraft. + -- @function [parent=#RECOVERYTANKER] __Refuel + -- @param #RECOVERYTANKER self + -- @param #number delay Delay in seconds. + -- @param Wrapper.Unit#UNIT receiver Unit receiving fuel from the tanker. + + + --- Triggers the FSM event "Run". Simply puts the group into "Running" state, e.g. after refueling ended. + -- @function [parent=#RECOVERYTANKER] Run + -- @param #RECOVERYTANKER self + + --- Triggers delayed the FSM event "Run". Simply puts the group into "Running" state, e.g. after refueling ended. + -- @function [parent=#RECOVERYTANKER] __Run + -- @param #RECOVERYTANKER self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "RTB" that sends the tanker home. -- @function [parent=#RECOVERYTANKER] RTB -- @param #RECOVERYTANKER self @@ -147,6 +177,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) -- @param #RECOVERYTANKER self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop" that stops the recovery tanker. Event handlers are stopped. -- @function [parent=#RECOVERYTANKER] Stop -- @param #RECOVERYTANKER self @@ -254,6 +285,55 @@ function RECOVERYTANKER:SetTakeoffAir() end +--- Enable respawning of tanker. Note that this is the default behaviour. +-- @param #RECOVERYTANKER self +-- @return #RECOVERYTANKER self +function RECOVERYTANKER:SetRespawnOn() + self.respawn=true + return self +end + +--- Disable respawning of tanker. +-- @param #RECOVERYTANKER self +-- @return #RECOVERYTANKER self +function RECOVERYTANKER:SetRespawnOff() + self.respawn=false + return self +end + +--- Set whether tanker shall be respawned or not. +-- @param #RECOVERYTANKER self +-- @param #boolean switch If true (or nil), tanker will be respawned. If false, tanker will not be respawned. +-- @return #RECOVERYTANKER self +function RECOVERYTANKER:SetRespawnOnOff(switch) + if switch==nil or switch==true then + self.respawn=true + else + self.respawn=false + end + return self +end + +--- Tanker will be respawned in air, even it was initially spawned on the carrier. +-- So only the first spawn will be on the carrier while all subsequent spawns will happen in air. +-- This allows for undisrupted operations and less problems on the carrier deck. +-- @param #RECOVERYTANKER self +-- @return #RECOVERYTANKER self +function RECOVERYTANKER:SetRespawnInAir() + self.respawninair=true + return self +end + +--- Use an uncontrolled aircraft already present in the mission rather than spawning a new tanker as initial recovery thanker. +-- This can be useful when interfaced with, e.g., a warehouse. +-- The group name is the one specified in the @{#RECOVERYTANKER.New} function. +-- @param #RECOVERYTANKER self +-- @return #RECOVERYTANKER self +function RECOVERYTANKER:SetUseUncontrolledAircraft() + self.uncontrolledac=true + return self +end + --- Check if tanker is returning to base. -- @param #RECOVERYTANKER self -- @return #boolean If true, tanker is returning to base. @@ -284,7 +364,9 @@ function RECOVERYTANKER:onafterStart(From, Event, To) -- Handle events. self:HandleEvent(EVENTS.EngineShutdown) - --TODO: Handle event crash and respawn. + self:HandleEvent(EVENTS.Refueling) + self:HandleEvent(EVENTS.RefuelingStop) + self:HandleEvent(EVENTS.Crash) -- Spawn tanker. local Spawn=SPAWN:New(self.tankergroupname):InitUnControlled(false) @@ -295,6 +377,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To) -- Carrier heading local hdg=self.carrier:GetHeading() + -- Spawn distance behind the carrier. local dist=UTILS.NMToMeters(20) -- Coordinate behind the carrier @@ -306,11 +389,32 @@ function RECOVERYTANKER:onafterStart(From, Event, To) -- Spawn at coordinate. self.tanker=Spawn:SpawnFromCoordinate(Carrier) + -- Initial route. self:_InitRoute(15, 1, 2) else - -- Spawn tanker at airbase. - self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff) + -- Check if an uncontrolled tanker group was requested. + if self.useuncontrolled then + + -- Use an uncontrolled aircraft group. + self.tanker=GROUP:FindByName(self.tankergroupname) + + if self.tanker:IsAlive() then + -- Start uncontrolled group. + self.tanker:StartUncontrolled() + else + self:E(string.format("ERROR: No uncontrolled (alive) tanker group with name %s could be found!", self.tankergroupname)) + return + end + + else + + -- Spawn tanker at airbase. + self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff) + + end + + -- Initialize route. self:_InitRoute(30, 10, 1) end @@ -331,10 +435,11 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) -- Get fuel of tanker. local fuel=self.tanker:GetFuel()*100 - local text=string.format("Tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel) + local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel) self:I(text) + -- Check if tanker is running and not RTBing. if self:IsRunning() then -- Check fuel. @@ -350,6 +455,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) --Time since last pattern update. local dt=time-self.Tupdate + -- Update pattern. if dt>self.dTupdate then self:_PatternUpdate() end @@ -363,16 +469,6 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) self:__Status(-60) end ---- On after Stop event. Unhandle events and stop status updates. --- @param #RECOVERYTANKER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. -function RECOVERYTANKER:onafterStop(From, Event, To) - self:UnHandleEvent(EVENTS.EngineShutdown) - --self:UnHandleEvent(EVENTS.Land) -end - --- On before RTB event. Check if takeoff type is air and if so respawn the tanker and deny RTB transition. -- @param #RECOVERYTANKER self -- @param #string From From state. @@ -381,27 +477,32 @@ end -- @return #boolean If true, transition is allowed. function RECOVERYTANKER:onbeforeRTB(From, Event, To) + -- Check if spawn in air is activated. if self.takeoff==SPAWN.Takeoff.Air then - -- Debug message. - local text=string.format("Respawning tanker %s.", self.tanker:GetName()) - self:I(text) - - -- Respawn tanker. - self.tanker:InitHeading(self.tanker:GetHeading()) - self.tanker=self.tanker:Respawn(nil, true) - - -- Update Pattern in 2 seconds. Need to give a bit time so that the respawned group is in the game. - SCHEDULER:New(nil, self._PatternUpdate, {self}, 2) - - -- Deny transition to RTB. - return false + -- Check that respawn should happen. + if self.respawn then + + -- Debug message. + local text=string.format("Respawning tanker %s.", self.tanker:GetName()) + self:I(text) + + -- Respawn tanker. + self.tanker:InitHeading(self.tanker:GetHeading()) + self.tanker=self.tanker:Respawn(nil, true) + + -- Update Pattern in 2 seconds. Need to give a bit time so that the respawned group is in the game. + SCHEDULER:New(nil, self._PatternUpdate, {self}, 2) + + -- Deny transition to RTB. + return false + end end return true end ---- On after RTB event. Send tanker back to carrier. +--- On after "RTB" event. Send tanker back to carrier. -- @param #RECOVERYTANKER self -- @param #string From From state. -- @param #string Event Event. @@ -412,17 +513,28 @@ function RECOVERYTANKER:onafterRTB(From, Event, To) local text=string.format("Tanker %s returning to airbase %s.", self.tanker:GetName(), self.airbase:GetName()) self:I(text) - local waypoints={} + -- Waypoint array. + local wp={} - -- Set landingwaypoint - local wp=self.carrier:GetCoordinate():WaypointAirLanding(300, self.airbase, nil, "Landing") - table.insert(waypoints, wp) + -- Set landing waypoint. + wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil, 300, {}, "Current Position") + wp[2]=self.carrier:GetCoordinate():WaypointAirLanding(300, self.airbase, nil, "Landing on Carrier") -- Initialize WP and route tanker. - self.tanker:WayPointInitialize(waypoints) + self.tanker:WayPointInitialize(wp) -- Set task. - self.tanker:Route(waypoints, 1) + self.tanker:Route(wp, 1) +end + +--- On after Stop event. Unhandle events and stop status updates. +-- @param #RECOVERYTANKER self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function RECOVERYTANKER:onafterStop(From, Event, To) + self:UnHandleEvent(EVENTS.EngineShutdown) + --self:UnHandleEvent(EVENTS.Land) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -437,7 +549,8 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) local group=EventData.IniGroup --Wrapper.Group#GROUP - if group:IsAlive() then + -- Check if group is alive and should be respawned. + if group:IsAlive() and self.respawn then -- Group name. When spawning it will have #001 attached. local groupname=group:GetName() @@ -449,9 +562,7 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) -- Respawn tanker. self.tanker=group:RespawnAtCurrentAirbase() - - --group:StartUncontrolled(60) - + -- Initial route. self:_InitRoute() end @@ -459,6 +570,57 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) end end +--- Event handler for refueling started. +-- @param #RECOVERYTANKER self +-- @param Core.Event#EVENTDATA EventData Event data. +function RECOVERYTANKER:OnEventRefuel(EventData) + + if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() then + + -- Unit receiving fuel. + local unit=EventData.IniUnit + + -- Get distance to tanker to check that unit is receiving fuel from this tanker. + local dist=unit:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate()) + + -- If distance > 100 meters, this should be another tanker. + if dist>100 then + return + end + + -- Info message. + self:I(string.format("Recovery tanker %s started refueling unit %s", self.tanker:GetName(), unit:GetName())) + + end + +end + +--- Event handler for refueling stopped. +-- @param #RECOVERYTANKER self +-- @param Core.Event#EVENTDATA EventData Event data. +function RECOVERYTANKER:OnEventRefuelStop(EventData) + + if EventData and EventData.IniUnit and EventData.IniUnit:IsAlive() then + + -- Unit receiving fuel. + local unit=EventData.IniUnit + + -- Get distance to tanker to check that unit is receiving fuel from this tanker. + local dist=unit:GetCoordinate():Get2DDistance(self.tanker:GetCoordinate()) + + -- If distance > 100 meters, this should be another tanker. + if dist>100 then + return + end + + -- Info message. + self:I(string.format("Recovery tanker %s stopped refueling unit %s", self.tanker:GetName(), unit:GetName())) + + end + +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ROUTE functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -476,7 +638,7 @@ function RECOVERYTANKER:_InitRoute(dist, Tstart, delay) delay=delay or 1 -- Debug message. - self:I(string.format("Initializing route for tanker %s.", self.tanker:GetName())) + self:I(string.format("Initializing route for recovery tanker %s.", self.tanker:GetName())) -- Carrier position. local Carrier=self.carrier:GetCoordinate() @@ -509,6 +671,9 @@ end --- Function to update the race-track pattern of the tanker wrt to the carrier position. -- @param #RECOVERYTANKER self function RECOVERYTANKER:_PatternUpdate() + + -- Debug message. + self:I(string.format("Updating recovery tanker %s orbit.", self.tanker:GetName())) -- Carrier heading. local hdg=self.carrier:GetHeading() @@ -517,38 +682,34 @@ function RECOVERYTANKER:_PatternUpdate() local Carrier=self.carrier:GetCoordinate() -- Define race-track pattern. + local p0=self.tanker:GetCoordinate():Translate(1000, self.tanker:GetHeading()) local p1=Carrier:SetAltitude(self.altitude):Translate(self.distStern, hdg) local p2=Carrier:SetAltitude(self.altitude):Translate(self.distBow, hdg) -- Set orbit task. local taskorbit=self.tanker:TaskOrbit(p1, self.altitude, self.speed, p2) - -- New waypoint. - local p0=self.tanker:GetCoordinate():Translate(1000, self.tanker:GetHeading()) - -- Debug markers. if self.Debug then - p0:MarkToAll("p0") - p1:MarkToAll("p1") - p2:MarkToAll("p2") + p0:MarkToAll("Waypoint P0 " ..self.tanker:GetName()) + p1:MarkToAll("Racetrack P1 "..self.tanker:GetName()) + p2:MarkToAll("Racetrack P2 "..self.tanker:GetName()) end - - -- Debug message. - self:I(string.format("Updating tanker %s orbit.", self.tanker:GetName())) - + -- Waypoints array. - local waypoints={} + local wp={} -- New waypoint with orbit pattern task. - local wp=p0:WaypointAirTurningPoint(nil, self.speed, {taskorbit}, "Tanker Orbit") - waypoints[1]=wp + wp[1]=self.tanker:GetCoordinate():WaypointAirTurningPoint(nil , self.speed, {}, "Current Position") + wp[2]=p0:WaypointAirTurningPoint(nil, self.speed, {taskorbit}, "Tanker Orbit") -- Initialize WP and route tanker. - self.tanker:WayPointInitialize(waypoints) + self.tanker:WayPointInitialize(wp) -- Task combo. local tasktanker = self.tanker:EnRouteTaskTanker() - local taskroute = self.tanker:TaskRoute(waypoints) + local taskroute = self.tanker:TaskRoute(wp) + -- Note that tasktanker has to come first. Otherwise it does not work! local taskcombo = self.tanker:TaskCombo({tasktanker, taskroute}) -- Set task. diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 6272d32e3..6e8a83d1d 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -1,11 +1,13 @@ --- **Functional** - (R2.5) - Rescue helo. -- --- Recue helicopter on an aircraft carrier. +-- Recue helicopter for carrier operations. -- -- Features: -- --- * Formation with carrier. --- * Automatic respawning on empty fuel. +-- * Close formation with carrier. +-- * Carrier can have any number of waypoints. +-- * Automatic respawning on empty fuel for 24/7 operations. +-- * Automatic rescuing of crashed or ejected units in the vicinity. -- -- Please not that his class is work in progress and in an **alpha** stage. -- @@ -24,13 +26,14 @@ -- @field #string helogroupname Name of the late activated helo template group. -- @field Wrapper.Group#GROUP helo Helo group. -- @field #number takeoff Takeoff type. --- @field Wrapper.Airbase#AIRBASE airbase The airbase object of the carrier. +-- @field Wrapper.Airbase#AIRBASE airbase The airbase object acting as home base of the helo. -- @field Core.Set#SET_GROUP followset Follow group set. -- @field AI.AI_Formation#AI_FORMATION formation AI_FORMATION object. -- @field #number lowfuel Low fuel threshold of helo in percent. -- @field #number altitude Altitude of helo in meters. -- @field #number offsetX Offset in meters to carrier in longitudinal direction. -- @field #number offsetZ Offset in meters to carrier in latitudinal direction. +-- @field Core.Zone#ZONE_RADIUS rescuezone Zone around the carrier in which helo will rescue crashed or ejected units. -- @extends Core.Fsm#FSM --- Rescue Helo @@ -45,7 +48,7 @@ -- -- @field #RESCUEHELO RESCUEHELO = { - ClassName = "RESCUEHELO", + ClassName = "RESCUEHELO", carrier = nil, carriertype = nil, helogroupname = nil, @@ -58,20 +61,22 @@ RESCUEHELO = { altitude = nil, offsetX = nil, offsetZ = nil, + rescuezone = nil, } --- Class version. -- @field #string version -RESCUEHELO.version="0.9.1" +RESCUEHELO.version="0.9.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Add option to stop carrier while rescue operation is in progress. -- TODO: Possibility to add already present/spawned aircraft, e.g. for warehouse. -- TODO: Write documenation. --- TODO: Add rescue event when aircraft crashes. --- TODO: Make offset input parameter. +-- DONE: Add rescue event when aircraft crashes. +-- DONE: Make offset input parameter. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -87,6 +92,7 @@ function RESCUEHELO:New(carrierunit, helogroupname) -- Inherit everthing from FSM class. local self = BASE:Inherit(self, FSM:New()) -- #RESCUEHELO + -- Catch case when just the unit name is passed. if type(carrierunit)=="string" then self.carrier=UNIT:FindByName(carrierunit) else @@ -98,10 +104,7 @@ function RESCUEHELO:New(carrierunit, helogroupname) -- Helo group name. self.helogroupname=helogroupname - - -- Home airbase of helo - self.airbase=AIRBASE:FindByName(self.carrier:GetName()) - + -- Init defaults. self:SetHomeBase(AIRBASE:FindByName(self.carrier:GetName())) self:SetTakeoffHot() @@ -109,6 +112,7 @@ function RESCUEHELO:New(carrierunit, helogroupname) self:SetAltitude() self:SetOffsetX() self:SetOffsetZ() + self:SetRescueZone() ----------------------- --- FSM Transitions --- @@ -120,10 +124,11 @@ function RESCUEHELO:New(carrierunit, helogroupname) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("Running", "Rescue", "Rescuing") self:AddTransition("Running", "RTB", "Returning") - self:AddTransition("Returning", "Status", "*") - self:AddTransition("Running", "Status", "*") - self:AddTransition("Running", "Stop", "Stopped") + self:AddTransition("*", "Run", "Running") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "Stopped") --- Triggers the FSM event "Start" that starts the rescue helo. Initializes parameters and starts event handlers. @@ -135,6 +140,17 @@ function RESCUEHELO:New(carrierunit, helogroupname) -- @param #RESCUEHELO self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Rescue" that sends the helo on a rescue mission to a specifc coordinate. + -- @function [parent=#RESCUEHELO] Rescue + -- @param #RESCUEHELO self + -- @param Core.Point#COORDINATE RescueCoord Coordinate where the resue mission takes place. + + --- Triggers the delayed FSM event "Rescue" that sends the helo on a rescue mission to a specifc coordinate. + -- @function [parent=#RESCUEHELO] __Rescue + -- @param #RESCUEHELO self + -- @param #number delay Delay in seconds. + -- @param Core.Point#COORDINATE RescueCoord Coordinate where the resue mission takes place. + --- Triggers the FSM event "RTB" that sends the helo home. -- @function [parent=#RESCUEHELO] RTB -- @param #RESCUEHELO self @@ -144,6 +160,15 @@ function RESCUEHELO:New(carrierunit, helogroupname) -- @param #RESCUEHELO self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Run". + -- @function [parent=#RESCUEHELO] Run + -- @param #RESCUEHELO self + + --- Triggers the delayed FSM event "Run". + -- @function [parent=#RESCUEHELO] __Run + -- @param #RESCUEHELO self + -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop" that stops the rescue helo. Event handlers are stopped. -- @function [parent=#RESCUEHELO] Stop -- @param #RESCUEHELO self @@ -178,12 +203,21 @@ function RESCUEHELO:SetHomeBase(airbase) return self end +--- Set rescue zone radius. Crashed or ejected units inside this radius of the carrier will be rescued. +-- @param #RESCUEHELO self +-- @param #number radius Radius of rescue zone in meters. Default is 100000 m = 100 km. +-- @return #RESCUEHELO self +function RESCUEHELO:SetRescueZone(radius) + self.rescuezone=ZONE_UNIT:New("Rescue Zone", self.carrier, radius or 100000) + return self +end + --- Set takeoff type. -- @param #RESCUEHELO self --- @param #number takeofftype Takeoff type. +-- @param #number takeofftype Takeoff type. Default SPAWN.Takeoff.Hot. -- @return #RESCUEHELO self function RESCUEHELO:SetTakeoff(takeofftype) - self.takeoff=takeofftype + self.takeoff=takeofftype or SPAWN.Takeoff.Hot return self end @@ -239,20 +273,109 @@ function RESCUEHELO:SetOffsetZ(distance) end ---- Check if tanker is returning to base. +--- Check if helo is returning to base. -- @param #RESCUEHELO self -- @return #boolean If true, helo is returning to base. function RESCUEHELO:IsReturning() return self:is("Returning") end ---- Check if tanker is operating. +--- Check if helo is operating. -- @param #RESCUEHELO self -- @return #boolean If true, helo is operating. function RESCUEHELO:IsRunning() return self:is("Running") end +--- Check if helo is on a rescue mission. +-- @param #RESCUEHELO self +-- @return #boolean If true, helo is rescuing somebody. +function RESCUEHELO:IsRescuing() + return self:is("Rescuing") +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- EVENT functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Handle landing event of rescue helo. +-- @param #RESCUEHELO self +-- @param Core.Event#EVENTDATA EventData Event data. +function RESCUEHELO:OnEventLand(EventData) + local group=EventData.IniGroup --Wrapper.Group#GROUP + + if group:IsAlive() then + local groupname=group:GetName() + + if groupname:match(self.helogroupname) then + + -- Respawn the Helo. + self:I(string.format("Respawning rescue helo group %s at home base.", groupname)) + + if self.takeoff==SPAWN.Takeoff.Air then + + self:E("ERROR: Rescue helo %s landed. This should not happen for Takeoff=Air!", groupname) + + else + + -- Respawn helo at current airbase. + self.helo=group:RespawnAtCurrentAirbase() + + end + + -- Restart the formation. + self:__Run(10) + end + end +end + +--- A unit crashed or a player ejected. +-- @param #RESCUEHELO self +-- @param Core.Event#EVENTDATA EventData Event data. +function RESCUEHELO:_OnEventCrashOrEject(EventData) + self:F2({eventdata=EventData}) + + -- NOTE: Careful here. Eject and crash events will probably happen for the same unit! + + -- Check that there is an initiating unit in the event data. + if EventData and EventData.IniUnit then + + -- Crashed or ejected unit. + local unit=EventData.IniUnit + local unitname=tostring(EventData.IniUnitName) + + -- Check that it was not the rescue helo itself that crashed. + if EventData.IniGroupName~=self.helo:GetName() then + + -- Debug. + self:T(string.format("Unit %s crashed or ejected.", unitname)) + + -- Unit "alive" and in our rescue zone. + if unit:IsAlive() and unit:IsInZone(self.rescuezone) then + + -- Get coordinate of crashed unit. + local coord=unit:GetCoordinate() + + -- Debug mark on map. + coord:MarkToCoalition(string.format("Crash site of unit %s.", unitname), self.helo:GetCoalition()) + + -- Only rescue if helo is "running" and not, e.g., rescuing already. + if self:IsRunning() then + self:Rescue(coord) + end + + end + + else + + self:I(string.format("Rescue helo %s crashed!", unitname)) + + end + + end + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -270,7 +393,8 @@ function RESCUEHELO:onafterStart(From, Event, To) -- Handle events. --self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.Land) - --self:HandleEvent(EVENTS.Crash) + self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrEject) + self:HandleEvent(EVENTS.Ejection, self._OnEventCrashOrEject) -- Delay before formation is started. local delay=120 @@ -356,7 +480,7 @@ function RESCUEHELO:onafterStatus(From, Event, To) self:I(text) -- If fuel < threshold ==> send helo to home base! - if fuel