From e3f4aa3d644ad7729ca91247a81725e01c17fa4e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 29 Jun 2020 21:05:26 +0200 Subject: [PATCH 1/2] Update FlightGroup.lua --- Moose Development/Moose/Ops/FlightGroup.lua | 1094 +++++++++---------- 1 file changed, 547 insertions(+), 547 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index d5fae6836..40c740a02 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -25,7 +25,7 @@ -- @field #number ceiling Max altitude the aircraft can fly at in meters. -- @field #number tankertype The refueling system type (0=boom, 1=probe), if the group is a tanker. -- @field #number refueltype The refueling system type (0=boom, 1=probe), if the group can refuel from a tanker. --- @field #FLIGHTGROUP.Ammo ammo Ammunition data. Number of Guns, Rockets, Bombs, Missiles. +-- @field #FLIGHTGROUP.Ammo ammo Ammunition data. Number of Guns, Rockets, Bombs, Missiles. -- @field #boolean ai If true, flight is purely AI. If false, flight contains at least one human player. -- @field #boolean fuellow Fuel low switch. -- @field #number fuellowthresh Low fuel threshold in percent. @@ -42,7 +42,7 @@ -- @field #table menu F10 radio menu. -- @field #string controlstatus Flight control status. -- @field #boolean ishelo If true, the is a helicopter group. --- +-- -- @extends Ops.OpsGroup#OPSGROUP --- *To invent an airplane is nothing. To build one is something. To fly is everything.* -- Otto Lilienthal @@ -54,78 +54,78 @@ -- # The FLIGHTGROUP Concept -- -- # Events --- +-- -- This class introduces a lot of additional events that will be handy in many situations. -- Certain events like landing, takeoff etc. are triggered for each element and also have a corresponding event when the whole group reaches this state. --- +-- -- ## Spawning --- +-- -- ## Parking --- +-- -- ## Taxiing --- +-- -- ## Takeoff --- +-- -- ## Airborne --- +-- -- ## Landed --- +-- -- ## Arrived --- +-- -- ## Dead --- +-- -- ## Fuel --- +-- -- ## Ammo --- +-- -- ## Detected Units --- +-- -- ## Check In Zone --- +-- -- ## Passing Waypoint --- --- +-- +-- -- # Tasking --- +-- -- The FLIGHTGROUP class significantly simplifies the monitoring of DCS tasks. Two types of tasks can be set --- +-- -- * **Scheduled Tasks** -- * **Waypoint Tasks** --- +-- -- ## Scheduled Tasks --- +-- -- ## Waypoint Tasks --- +-- -- # Missions --- +-- -- ## Anti-ship --- +-- -- ## AWACS --- +-- -- ## INTERCEPT --- --- +-- +-- -- # Examples --- +-- -- Here are some examples to show how things are done. --- +-- -- ## 1. Spawn --- +-- -- ## 2. Attack Group --- +-- -- ## 3. Whatever --- +-- -- ## 4. Simple Tanker --- +-- -- ## 5. Simple AWACS --- +-- -- ## 6. Scheduled Tasks --- +-- -- ## 7. Waypoint Tasks --- +-- -- ## 8. Enroute Tasks --- --- +-- +-- -- -- -- @field #FLIGHTGROUP @@ -142,7 +142,7 @@ FLIGHTGROUP = { fuellow = false, fuellowthresh = nil, fuellowrtb = nil, - fuelcritical = nil, + fuelcritical = nil, fuelcriticalthresh = nil, fuelcriticalrtb = false, squadron = nil, @@ -242,7 +242,7 @@ function FLIGHTGROUP:New(group) -- Inherit everything from FSM class. local self=BASE:Inherit(self, OPSGROUP:New(group)) -- #FLIGHTGROUP - + -- Set some string id for output to DCS.log file. self.lid=string.format("FLIGHTGROUP %s | ", self.groupname) @@ -253,34 +253,34 @@ function FLIGHTGROUP:New(group) self:SetDefaultROT() self:SetDetection() - -- Holding flag. + -- Holding flag. self.flaghold=USERFLAG:New(string.format("%s_FlagHold", self.groupname)) self.flaghold:Set(0) -- Add FSM transitions. - -- From State --> Event --> To State + -- From State --> Event --> To State self:AddTransition("*", "RTB", "Inbound") -- Group is returning to destination base. self:AddTransition("*", "RTZ", "Inbound") -- Group is returning to destination zone. Not implemented yet! self:AddTransition("Inbound", "Holding", "Holding") -- Group is in holding pattern. - + self:AddTransition("*", "Refuel", "Going4Fuel") -- Group is send to refuel at a tanker. Not implemented yet! self:AddTransition("Going4Fuel", "Refueled", "Airborne") -- Group is send to refuel at a tanker. Not implemented yet! - + self:AddTransition("*", "LandAt", "LandingAt") -- Helo group is ordered to land at a specific point. self:AddTransition("LandingAt", "LandedAt", "LandedAt") -- Helo group landed landed at a specific point. self:AddTransition("*", "Wait", "Waiting") -- Group is orbiting. - + self:AddTransition("*", "FuelLow", "*") -- Fuel state of group is low. Default ~25%. self:AddTransition("*", "FuelCritical", "*") -- Fuel state of group is critical. Default ~10%. - + self:AddTransition("*", "OutOfMissilesAA", "*") -- Group is out of A2A missiles. Not implemented yet! self:AddTransition("*", "OutOfMissilesAG", "*") -- Group is out of A2G missiles. Not implemented yet! self:AddTransition("*", "OutOfMissilesAS", "*") -- Group is out of A2G missiles. Not implemented yet! self:AddTransition("Airborne", "EngageTargets", "Engaging") -- Engage targets. self:AddTransition("Engaging", "Disengage", "Airborne") -- Engagement over. - + self:AddTransition("*", "ElementParking", "*") -- An element is parking. self:AddTransition("*", "ElementEngineOn", "*") -- An element spooled up the engines. @@ -292,8 +292,8 @@ function FLIGHTGROUP:New(group) self:AddTransition("*", "ElementOutOfAmmo", "*") -- An element is completely out of ammo. - - + + self:AddTransition("*", "FlightParking", "Parking") -- The whole flight group is parking. self:AddTransition("*", "FlightTaxiing", "Taxiing") -- The whole flight group is taxiing. self:AddTransition("*", "FlightTakeoff", "Airborne") -- The whole flight group is airborne. @@ -323,10 +323,10 @@ function FLIGHTGROUP:New(group) BASE:TraceClass(self.ClassName) BASE:TraceLevel(1) end - + -- Add to data base. _DATABASE:AddFlightGroup(self) - + -- Handle events: self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.EngineStartup, self.OnEventEngineStartup) @@ -341,7 +341,7 @@ function FLIGHTGROUP:New(group) -- Init waypoints. self:InitWaypoints() - + -- Initialize group. self:_InitGroup() @@ -389,7 +389,7 @@ end -- @param Ops.FlightControl#FLIGHTCONTROL flightcontrol The FLIGHTCONTROL object. -- @return #FLIGHTGROUP self function FLIGHTGROUP:SetFlightControl(flightcontrol) - + -- Check if there is already a FC. if self.flightcontrol then if self.flightcontrol.airbasename==flightcontrol.airbasename then @@ -400,19 +400,19 @@ function FLIGHTGROUP:SetFlightControl(flightcontrol) self.flightcontrol:_RemoveFlight(self) end end - + -- Set FC. self:I(self.lid..string.format("Setting FLIGHTCONTROL to airbase %s", flightcontrol.airbasename)) self.flightcontrol=flightcontrol - + -- Add flight to all flights. table.insert(flightcontrol.flights, self) - + -- Update flight's F10 menu. if self.ai==false then self:_UpdateMenu(0.5) end - + return self end @@ -625,7 +625,7 @@ function FLIGHTGROUP:StartUncontrolled(delay) else self:E(self.lid.."ERROR: Could not start uncontrolled group as it is NOT alive!") end - + end return self @@ -640,12 +640,12 @@ function FLIGHTGROUP:ClearToLand(Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, FLIGHTGROUP.ClearToLand, self) else - + if self:IsHolding() then - self:I(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) + self:I(self.lid..string.format("Clear to land ==> setting holding flag to 1 (true)")) self.flaghold:Set(1) end - + end return self end @@ -658,20 +658,20 @@ function FLIGHTGROUP:GetFuelMin() local fuelmin=math.huge for i,_element in pairs(self.elements) do local element=_element --#FLIGHTGROUP.Element - + local unit=element.unit - + local life=unit:GetLife() - + if unit and unit:IsAlive() and life>1 then local fuel=unit:GetFuel() if fuel10 meters, we consider the unit as taxiing. -- TODO: Check distance threshold! If element is taxiing, the parking spot is free again. -- When the next plane is spawned on this spot, collisions should be avoided! @@ -719,49 +719,49 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) self:ElementTaxiing(element) end end - + else --self:E(self.lid..string.format("Element %s is in PARKING queue but has no parking spot assigned!", element.name)) end - end + end end - + --- -- Elements --- - + local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks() local nMissions=self:CountRemainingMissison() -- Short info. - if self.verbose>0 then + if self.verbose>0 then local text=string.format("Status %s [%d/%d]: Tasks=%d (%d,%d) Current=%d. Missions=%s. Waypoint=%d/%d. Detected=%d. Destination=%s, FC=%s", - fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, + fsmstate, #self.elements, #self.elements, nTaskTot, nTaskSched, nTaskWP, self.taskcurrent, nMissions, self.currentwp or 0, self.waypoints and #self.waypoints or 0, self.detectedunits:Count(), self.destbase and self.destbase:GetName() or "unknown", self.flightcontrol and self.flightcontrol.airbasename or "none") self:I(self.lid..text) end -- Element status. - if self.verbose>1 then + if self.verbose>1 then local text="Elements:" for i,_element in pairs(self.elements) do local element=_element --#FLIGHTGROUP.Element - + local name=element.name local status=element.status local unit=element.unit local fuel=unit:GetFuel() or 0 local life=unit:GetLifeRelative() or 0 local parking=element.parking and tostring(element.parking.TerminalID) or "X" - + -- Check if element is not dead and we missed an event. if life<0 and element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then self:ElementDead(element) end - + -- Get ammo. local ammo=self:GetAmmoElement(element) - + -- Output text for element. text=text..string.format("\n[%d] %s: status=%s, fuel=%.1f, life=%.1f, guns=%d, rockets=%d, bombs=%d, missiles=%d (AA=%d, AG=%d, AS=%s), parking=%s", i, name, status, fuel*100, life*100, ammo.Guns, ammo.Rockets, ammo.Bombs, ammo.Missiles, ammo.MissilesAA, ammo.MissilesAG, ammo.MissilesAS, parking) @@ -779,67 +779,67 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) if self.verbose>1 and self:IsAlive() and self.position then local time=timer.getAbsTime() - + -- Current position. local position=self:GetCoordinate() - + -- Travelled distance since last check. local ds=self.position:Get3DDistance(position) - + -- Time interval. local dt=time-self.traveltime - + -- Speed. local v=ds/dt - + -- Add up travelled distance. self.traveldist=self.traveldist+ds - - + + -- Max fuel time remaining. local TmaxFuel=math.huge - + for _,_element in pairs(self.elements) do local element=_element --#FLIGHTGROUP.Element - + -- Get relative fuel of element. local fuel=element.unit:GetFuel() or 0 - + -- Relative fuel used since last check. local dFrel=element.fuelrel-fuel - + -- Relative fuel used per second. local dFreldt=dFrel/dt - + -- Fuel remaining in seconds. local Tfuel=fuel/dFreldt - + if Tfuel Tfuel=%.1f min", element.name, fuel*100, dFrel*100, dFreldt*100*60, Tfuel/60)) - + -- Store rel fuel. element.fuelrel=fuel end - + -- Log outut. self:I(self.lid..string.format("Travelled ds=%.1f km dt=%.1f s ==> v=%.1f knots. Fuel left for %.1f min", self.traveldist/1000, dt, UTILS.MpsToKnots(v), TmaxFuel/60)) - + -- Update parameters. self.traveltime=time - self.position=position - end - + self.position=position + end + --- -- Tasks --- - + -- Task queue. - if #self.taskqueue>0 and self.verbose>1 then + if #self.taskqueue>0 and self.verbose>1 then local text=string.format("Tasks #%d", #self.taskqueue) for i,_task in pairs(self.taskqueue) do local task=_task --#FLIGHTGROUP.Task @@ -869,22 +869,22 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) end self:I(self.lid..text) end - + --- -- Missions --- - + -- Current mission name. - if self.verbose>0 then + if self.verbose>0 then local Mission=self:GetMissionByID(self.currentmission) - + -- Current status. local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none") for i,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG local Cstart= UTILS.SecondsToClock(mission.Tstart, true) local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF" - text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", + text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d", i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets()) end self:I(self.lid..text) @@ -893,31 +893,31 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) --- -- Fuel State --- - + -- Only if group is in air. if self:IsAlive() and self.group:IsAirborne(true) then local fuelmin=self:GetFuelMin() - + if fuelmin>=self.fuellowthresh then self.fuellow=false end - + if fuelmin>=self.fuelcriticalthresh then self.fuelcritical=false end - - + + -- Low fuel? if fuelmin1 groups to have passed. -- TODO: Can I do this more rigorously? self:ScheduleOnce(1, reset) - + else - + -- Set homebase if not already set. if EventData.Place then self.homebase=self.homebase or EventData.Place end - + -- Get element. local element=self:GetElementByName(unitname) - + -- Create element spawned event if not already present. if not self:_IsElement(unitname) then element=self:AddElementByName(unitname) end - + -- Set element to spawned state. - self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) + self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name)) self:ElementSpawned(element) - - end - + + end + end end @@ -1011,7 +1011,7 @@ function FLIGHTGROUP:OnEventEngineStartup(EventData) local element=self:GetElementByName(unitname) if element then - + if self:IsAirborne() or self:IsInbound() or self:IsHolding() then -- TODO: what? else @@ -1028,7 +1028,7 @@ function FLIGHTGROUP:OnEventEngineStartup(EventData) end end end - + end end @@ -1103,12 +1103,12 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) local element=self:GetElementByName(unitname) if element then - + if element.unit and element.unit:IsAlive() then - + local airbase=element.unit:GetCoordinate():GetClosestAirbase() local parking=self:GetParkingSpot(element, 10, airbase) - + if airbase and parking then self:ElementArrived(element, airbase, parking) self:T3(self.lid..string.format("EVENT: Element %s shut down engines ==> arrived", element.name)) @@ -1116,13 +1116,13 @@ function FLIGHTGROUP:OnEventEngineShutdown(EventData) self:T3(self.lid..string.format("EVENT: Element %s shut down engines (in air) ==> dead", element.name)) self:ElementDead(element) end - + else - + self:I(self.lid..string.format("EVENT: Element %s shut down engines but is NOT alive ==> waiting for crash event (==> dead)", element.name)) end - + else -- element is nil end @@ -1164,7 +1164,7 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then self:I(self.lid..string.format("EVENT: Unit %s lost!", EventData.IniUnitName)) end - + end --- Flightgroup event function handling the crash of a unit. @@ -1183,7 +1183,7 @@ function FLIGHTGROUP:OnEventRemoveUnit(EventData) if element then self:I(self.lid..string.format("EVENT: Element %s removed ==> dead", element.name)) - self:ElementDead(element) + self:ElementDead(element) end end @@ -1210,15 +1210,15 @@ function FLIGHTGROUP:onafterElementSpawned(From, Event, To, Element) -- Trigger ElementAirborne event. Add a little delay because spawn is also delayed! self:__ElementAirborne(0.11, Element) else - + -- Get parking spot. local spot=self:GetParkingSpot(Element, 10) - + if spot then - + -- Trigger ElementParking event. Add a little delay because spawn is also delayed! self:__ElementParking(0.11, Element, spot) - + else -- TODO: This can happen if spawned on deck of a carrier! self:E(self.lid..string.format("Element spawned not in air but not on any parking spot.")) @@ -1236,14 +1236,14 @@ end -- @param #FLIGHTGROUP.ParkingSpot Spot Parking Spot. function FLIGHTGROUP:onafterElementParking(From, Event, To, Element, Spot) self:T(self.lid..string.format("Element parking %s at spot %s", Element.name, Element.parking and tostring(Element.parking.TerminalID) or "N/A")) - + -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.PARKING) - + if Spot then self:_SetElementParkingAt(Element, Spot) end - + if self:IsTakeoffCold() then -- Wait for engine startup event. elseif self:IsTakeoffHot() then @@ -1284,7 +1284,7 @@ function FLIGHTGROUP:onafterElementTaxiing(From, Event, To, Element) -- Set parking spot to free. Also for FC. self:_SetElementParkingFree(Element) - + -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAXIING) end @@ -1306,7 +1306,7 @@ function FLIGHTGROUP:onafterElementTakeoff(From, Event, To, Element, airbase) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.TAKEOFF, airbase) - + -- Trigger element airborne event. self:__ElementAirborne(2, Element) end @@ -1333,14 +1333,14 @@ end -- @param Wrapper.Airbase#AIRBASE airbase The airbase if applicable or nil. function FLIGHTGROUP:onafterElementLanded(From, Event, To, Element, airbase) self:T2(self.lid..string.format("Element landed %s at %s airbase", Element.name, airbase and airbase:GetName() or "unknown")) - + -- Helos with skids land directly on parking spots. if self.ishelo then - + local Spot=self:GetParkingSpot(Element, 10, airbase) - + self:_SetElementParkingAt(Element, Spot) - + end -- Set element status. @@ -1356,10 +1356,10 @@ end -- @param Wrapper.Airbase#AIRBASE airbase The airbase, where the element arrived. -- @param Wrapper.Airbase#AIRBASE.ParkingSpot Parking The Parking spot the element has. function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Parking) - self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99)) - + self:T(self.lid..string.format("Element arrived %s at %s airbase using parking spot %d", Element.name, airbase and airbase:GetName() or "unknown", Parking and Parking.TerminalID or -99)) + self:_SetElementParkingAt(Element, Parking) - + -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) end @@ -1375,8 +1375,8 @@ function FLIGHTGROUP:onafterElementDead(From, Event, To, Element) if self.flightcontrol and Element.parking then self.flightcontrol:SetParkingFree(Element.parking) - end - + end + -- Not parking any more. Element.parking=nil @@ -1392,39 +1392,39 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterSpawned(From, Event, To) self:I(self.lid..string.format("Flight spawned!")) - + if self.ai then - + -- Set default ROE and ROT options. self:SetOptionROE(self.roe) self:SetOptionROT(self.rot) - + -- TODO: make this input. self.group:SetOption(AI.Option.Air.id.PROHIBIT_JETT, true) self.group:SetOption(AI.Option.Air.id.PROHIBIT_AB, true) -- Does not seem to work. AI still used the after burner. self.group:SetOption(AI.Option.Air.id.RTB_ON_BINGO, false) --self.group:SetOption(AI.Option.Air.id.RADAR_USING, AI.Option.Air.val.RADAR_USING.FOR_CONTINUOUS_SEARCH) - + -- Turn TACAN beacon on. if self.tacanChannelDefault then self:SwitchTACANOn(self.tacanChannelDefault, self.tacanMorseDefault) end - + -- Turn on the radio. if self.radioFreqDefault then self:SwitchRadioOn(self.radioFreqDefault, self.radioModuDefault) end - + -- Update route. self:__UpdateRoute(-0.5) - + else - + -- F10 other menu. self:_UpdateMenu() - - end - + + end + end --- On after "FlightParking" event. Add flight to flightcontrol of airbase. @@ -1436,32 +1436,32 @@ function FLIGHTGROUP:onafterFlightParking(From, Event, To) self:I(self.lid..string.format("Flight is parking")) local airbase=self.group:GetCoordinate():GetClosestAirbase() - + local airbasename=airbase:GetName() or "unknown" - + -- Parking time stamp. self.Tparking=timer.getAbsTime() -- Get FC of this airbase. local flightcontrol=_DATABASE:GetFlightControl(airbasename) - + if flightcontrol then - + -- Set FC for this flight self:SetFlightControl(flightcontrol) - + if self.flightcontrol then - + -- Set flight status. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.PARKING) - + -- Update player menu. if not self.ai then self:_UpdateMenu(0.5) end - + end - end + end end --- On after "FlightTaxiing" event. @@ -1471,18 +1471,18 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterFlightTaxiing(From, Event, To) self:T(self.lid..string.format("Flight is taxiing")) - + -- Parking over. self.Tparking=nil -- TODO: need a better check for the airbase. local airbase=self.group:GetCoordinate():GetClosestAirbase(nil, self.group:GetCoalition()) - if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then + if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then -- Add AI flight to takeoff queue. if self.ai then - -- AI flights go directly to TAKEOFF as we don't know when they finished taxiing. + -- AI flights go directly to TAKEOFF as we don't know when they finished taxiing. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAKEOFF) else -- Human flights go to TAXI OUT queue. They will go to the ready for takeoff queue when they request it. @@ -1490,7 +1490,7 @@ function FLIGHTGROUP:onafterFlightTaxiing(From, Event, To) -- Update menu. self:_UpdateMenu() end - + end end @@ -1509,7 +1509,7 @@ function FLIGHTGROUP:onafterFlightTakeoff(From, Event, To, airbase) self.flightcontrol:_RemoveFlight(self) self.flightcontrol=nil end - + end --- On after "FlightAirborne" event. @@ -1519,7 +1519,7 @@ end -- @param #string To To state. function FLIGHTGROUP:onafterFlightAirborne(From, Event, To) self:I(self.lid..string.format("Flight airborne")) - + if not self.ai then self:_UpdateMenu() end @@ -1534,7 +1534,7 @@ function FLIGHTGROUP:onafterFlightLanding(From, Event, To) self:T(self.lid..string.format("Flight is landing")) self:_SetElementStatusAll(OPSGROUP.ElementStatus.LANDING) - + end --- On after "FlightLanded" event. @@ -1567,11 +1567,11 @@ function FLIGHTGROUP:onafterFlightArrived(From, Event, To) -- Flight Control if self.flightcontrol then -- Add flight to arrived queue. - self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) + self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.ARRIVED) end - + -- Stop and despawn in 5 min. - self:__Stop(5*60) + self:__Stop(5*60) end --- On after "FlightDead" event. @@ -1585,22 +1585,22 @@ function FLIGHTGROUP:onafterFlightDead(From, Event, To) -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil self.groupinitialized=false - + -- Remove flight from all FC queues. - if self.flightcontrol then + if self.flightcontrol then self.flightcontrol:_RemoveFlight(self) self.flightcontrol=nil end - - -- Cancel all mission. + + -- Cancel all mission. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG - + self:MissionCancel(mission) mission:FlightDead(self) - + end - + -- Stop self:Stop() end @@ -1629,48 +1629,48 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n) else -- Not airborne yet. Try again in 1 sec. self:T3(self.lid.."FF update route denied ==> checking back in 5 sec") - trepeat=-5 + trepeat=-5 allowed=false end - + if n and n<1 then self:E(self.lid.."Update route denied because waypoint n<1!") - allowed=false + allowed=false end - + if not self.currentwp then self:E(self.lid.."Update route denied because self.currentwp=nil!") - allowed=false + allowed=false end - + local N=n or self.currentwp+1 if not N or N<1 then self:E(self.lid.."FF update route denied because N=nil or N<1") trepeat=-5 - allowed=false + allowed=false end - + if self.taskcurrent>0 then self:E(self.lid.."Update route denied because taskcurrent>0") allowed=false end - + -- Not good, because mission will never start. Better only check if there is a current task! --if self.currentmission then --end - -- Only AI flights. + -- Only AI flights. if not self.ai then allowed=false end - + -- Debug info. self:T2(self.lid..string.format("Onbefore Updateroute allowed=%s state=%s repeat in %s", tostring(allowed), self:GetState(), tostring(trepeat))) - + if trepeat then self:__UpdateRoute(trepeat, n) end - + return allowed end @@ -1684,44 +1684,44 @@ function FLIGHTGROUP:onafterUpdateRoute(From, Event, To, n) -- Update route from this waypoint number onwards. n=n or self.currentwp+1 - + -- Update waypoint tasks, i.e. inject WP tasks into waypoint table. self:_UpdateWaypointTasks() -- Waypoints. local wp={} - + -- Current velocity. local speed=self.group and self.group:GetVelocityKMH() or 100 - + -- Set current waypoint or we get problem that the _PassingWaypoint function is triggered too early, i.e. right now and not when passing the next WP. local current=self.group:GetCoordinate():WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speed, true, nil, {}, "Current") table.insert(wp, current) - + -- Add remaining waypoints to route. for i=n, #self.waypoints do table.insert(wp, self.waypoints[i]) end - + -- Debug info. local hb=self.homebase and self.homebase:GetName() or "unknown" local db=self.destbase and self.destbase:GetName() or "unknown" self:T(self.lid..string.format("Updating route for WP #%d-%d homebase=%s destination=%s", n, #wp, hb, db)) - + if #wp>1 then -- Route group to all defined waypoints remaining. self:Route(wp, 1) - + else - + --- -- No waypoints left --- - + self:_CheckGroupDone() - + end end @@ -1737,7 +1737,7 @@ function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) self:T(self.lid.."Respawning group!") local template=UTILS.DeepCopy(Template or self.template) - + if self.group and self.group:InAir() then template.lateActivation=false self.respawning=true @@ -1747,13 +1747,13 @@ function FLIGHTGROUP:onafterRespawn(From, Event, To, Template) end --- Check if flight is done, i.e. --- --- * passed the final waypoint, +-- +-- * passed the final waypoint, -- * no current task -- * no current mission -- * number of remaining tasks is zero -- * number of remaining missions is zero --- +-- -- @param #FLIGHTGROUP self -- @param #number delay Delay in seconds. function FLIGHTGROUP:_CheckGroupDone(delay) @@ -1764,29 +1764,29 @@ function FLIGHTGROUP:_CheckGroupDone(delay) -- Delayed call. self:ScheduleOnce(delay, FLIGHTGROUP._CheckGroupDone, self) else - - -- First check if there is a paused mission that + + -- First check if there is a paused mission that if self.missionpaused then self:UnpauseMission() return end - - + + -- Number of tasks remaining. local nTasks=self:CountRemainingTasks() - + -- Number of mission remaining. local nMissions=self:CountRemainingMissison() - + -- Final waypoint passed? if self.passedfinalwp then - + -- Got current mission or task? if self.currentmission==nil and self.taskcurrent==0 then - + -- Number of remaining tasks/missions? if nTasks==0 and nMissions==0 then - + -- Send flight to destination. if self.destbase then self:I(self.lid.."Passed Final WP and No current and/or future missions/task ==> RTB!") @@ -1796,24 +1796,24 @@ function FLIGHTGROUP:_CheckGroupDone(delay) self:__RTZ(-3, self.destzone) else self:I(self.lid.."Passed Final WP and NO Tasks/Missions left. No DestBase or DestZone ==> Wait!") - self:__Wait(-1) + self:__Wait(-1) end - + else self:I(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions)) - self:__Wait(-1) + self:__Wait(-1) end else self:I(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission))) - end + end else self:I(self.lid..string.format("Flight (status=%s) did NOT pass the final waypoint yet ==> update route", self:GetState())) self:__UpdateRoute(-1) - end + end end - + end - + end --- On before "RTB" event. @@ -1825,9 +1825,9 @@ end -- @param #number SpeedTo Speed used for travelling from current position to holding point in knots. -- @param #number SpeedHold Holding speed in knots. function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) - + if self:IsAlive() then - + local allowed=true local Tsuspend=nil @@ -1835,56 +1835,56 @@ function FLIGHTGROUP:onbeforeRTB(From, Event, To, airbase, SpeedTo, SpeedHold) self:E(self.lid.."ERROR: Airbase is nil in RTB() call!") allowed=false end - + -- Check that coaliton is okay. We allow same (blue=blue, red=red) or landing on neutral bases. if airbase and airbase:GetCoalition()~=self.group:GetCoalition() and airbase:GetCoalition()>0 then self:E(self.lid..string.format("ERROR: Wrong airbase coalition %d in RTB() call! We allow only same as group %d or neutral airbases 0.", airbase:GetCoalition(), self.group:GetCoalition())) allowed=false - end - + end + if not self.group:IsAirborne(true) then self:I(self.lid..string.format("WARNING: Group is not AIRBORNE ==> RTB event is suspended for 10 sec.")) allowed=false - Tsuspend=-10 + Tsuspend=-10 end - + -- Only if fuel is not low or critical. if not (self:IsFuelLow() or self:IsFuelCritical()) then - + -- Check if there are remaining tasks. local Ntot,Nsched, Nwp=self:CountRemainingTasks() - + if self.taskcurrent>0 then self:I(self.lid..string.format("WARNING: Got current task ==> RTB event is suspended for 10 sec.")) Tsuspend=-10 allowed=false end - + if Nsched>0 then self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> RTB event is suspended for 10 sec.", Nsched)) Tsuspend=-10 allowed=false end - + if Nwp>0 then self:I(self.lid..string.format("WARNING: Still got %d WAYPOINT tasks in the queue ==> RTB event is suspended for 10 sec.", Nwp)) Tsuspend=-10 allowed=false end - + end - + if Tsuspend and not allowed then - self:__RTB(Tsuspend, airbase, SpeedTo, SpeedHold) + self:__RTB(Tsuspend, airbase, SpeedTo, SpeedHold) end - + return allowed - + else self:E(self.lid.."WARNING: Group is not alive! RTB call not allowed.") return false end - + end --- On after "RTB" event. Order flight to hold at an airbase and wait for signal to land. @@ -1899,13 +1899,13 @@ end function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, SpeedLand) self:I(self.lid..string.format("RTB: event=%s: %s --> %s to %s", Event, From, To, airbase:GetName())) - + -- Set the destination base. self.destbase=airbase - + -- Clear holding time in any case. self.Tholding=nil - + -- Defaults: SpeedTo=SpeedTo or UTILS.KmphToKnots(self.speedCruise) SpeedHold=SpeedHold or (self.ishelo and 80 or 250) @@ -1917,13 +1917,13 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp self:T(self.lid..text) local althold=self.ishelo and 1000+math.random(10)*100 or math.random(4,10)*1000 - + -- Holding points. local c0=self.group:GetCoordinate() local p0=airbase:GetZone():GetRandomCoordinate():SetAltitude(UTILS.FeetToMeters(althold)) local p1=nil local wpap=nil - + -- Do we have a flight control? local fc=_DATABASE:GetFlightControl(airbase:GetName()) if fc then @@ -1931,37 +1931,37 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp local HoldingPoint=fc:_GetHoldingpoint(self) p0=HoldingPoint.pos0 p1=HoldingPoint.pos1 - + -- Debug marks. if self.Debug then p0:MarkToAll("Holding point P0") p1:MarkToAll("Holding point P1") end - + -- Set flightcontrol for this flight. self:SetFlightControl(fc) - + -- Add flight to inbound queue. self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.INBOUND) end - + -- Altitude above ground for a glide slope of 3 degrees. local x1=self.ishelo and UTILS.NMToMeters(5.0) or UTILS.NMToMeters(10) local x2=self.ishelo and UTILS.NMToMeters(2.5) or UTILS.NMToMeters(5) - local alpha=math.rad(3) + local alpha=math.rad(3) local h1=x1*math.tan(alpha) local h2=x2*math.tan(alpha) - + local runway=airbase:GetActiveRunway() - + -- Set holding flag to 0=false. self.flaghold:Set(0) - + local holdtime=5*60 if fc or self.airboss then holdtime=nil end - + -- Task fuction when reached holding point. local TaskArrived=self.group:TaskFunction("FLIGHTGROUP._ReachedHolding", self) @@ -1970,72 +1970,72 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp local TaskLand = self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1, nil, holdtime) local TaskHold = self.group:TaskControlled(TaskOrbit, TaskLand) local TaskKlar = self.group:TaskFunction("FLIGHTGROUP._ClearedToLand", self) -- Once the holding flag becomes true, set trigger FLIGHTLANDING, i.e. set flight STATUS to LANDING. - + -- Waypoints from current position to holding point. local wp={} wp[#wp+1]=c0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {}, "Current Pos") wp[#wp+1]=p0:WaypointAir(nil, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, UTILS.KnotsToKmph(SpeedTo), true , nil, {TaskArrived, TaskHold, TaskKlar}, "Holding Point") - + -- Approach point: 10 NN in direction of runway. if airbase:GetAirbaseCategory()==Airbase.Category.AIRDROME then - + --- -- Airdrome --- local papp=airbase:GetCoordinate():Translate(x1, runway.heading-180):SetAltitude(h1) - wp[#wp+1]=papp:WaypointAirTurningPoint(nil, UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach") - + wp[#wp+1]=papp:WaypointAirTurningPoint(nil, UTILS.KnotsToKmph(SpeedLand), {}, "Final Approach") + -- Okay, it looks like it's best to specify the coordinates not at the airbase but a bit away. This causes a more direct landing approach. - local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) + local pland=airbase:GetCoordinate():Translate(x2, runway.heading-180):SetAltitude(h2) wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - + elseif airbase:GetAirbaseCategory()==Airbase.Category.SHIP then - + --- -- Ship --- - local pland=airbase:GetCoordinate() + local pland=airbase:GetCoordinate() wp[#wp+1]=pland:WaypointAirLanding(UTILS.KnotsToKmph(SpeedLand), airbase, {}, "Landing") - - end - + + end + if self.ai then - + local routeto=false if fc or world.event.S_EVENT_KILL then routeto=true end - + -- Clear all tasks. self:ClearTasks() - + -- Respawn? if routeto then - + --self:I(self.lid.."FF route (not repawn)") - + -- Just route the group. Respawn might happen when going from holding to final. self:Route(wp, 1) - - else - + + else + --self:I(self.lid.."FF respawn (not route)") - + -- Get group template. local Template=self.group:GetTemplate() - + -- Set route points. Template.route.points=wp - + --Respawn the group with new waypoints. self:Respawn(Template) - - end - + + end + end - + end --- On before "Wait" event. @@ -2059,7 +2059,7 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed) Tsuspend=-10 allowed=false end - + if Nsched>0 then self:I(self.lid..string.format("WARNING: Still got %d SCHEDULED tasks in the queue ==> WAIT event is suspended for 10 sec.", Nsched)) Tsuspend=-10 @@ -2071,11 +2071,11 @@ function FLIGHTGROUP:onbeforeWait(From, Event, To, Coord, Altitude, Speed) Tsuspend=-10 allowed=false end - + if Tsuspend and not allowed then self:__Wait(Tsuspend, Coord, Altitude, Speed) end - + return allowed end @@ -2089,7 +2089,7 @@ end -- @param #number Altitude Altitude in feet. Default 10000 ft. -- @param #number Speed Speed in knots. Default 250 kts. function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) - + Coord=Coord or self.group:GetCoordinate() Altitude=Altitude or (self.ishelo and 1000 or 10000) Speed=Speed or (self.ishelo and 80 or 250) @@ -2101,12 +2101,12 @@ function FLIGHTGROUP:onafterWait(From, Event, To, Coord, Altitude, Speed) --TODO: set ROE passive. introduce roe event/state/variable. - -- Orbit task. + -- Orbit task. local TaskOrbit=self.group:TaskOrbit(Coord, UTILS.FeetToMeters(Altitude), UTILS.KnotsToMps(Speed)) -- Set task. self:SetTask(TaskOrbit) - + end @@ -2117,7 +2117,7 @@ end -- @param #string To To state. -- @param Core.Point#COORDINATE Coordinate The coordinate. function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) - + -- Debug message. local text=string.format("Flight group set to refuel at the nearest tanker") MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) @@ -2133,18 +2133,18 @@ function FLIGHTGROUP:onafterRefuel(From, Event, To, Coordinate) local TaskRefuel=self.group:TaskRefueling() local TaskFunction=self.group:TaskFunction("FLIGHTGROUP._FinishedRefuelling", self) local DCSTasks={TaskRefuel, TaskFunction} - + local Speed=self.speedCruise local coordinate=self.group:GetCoordinate() - + Coordinate=Coordinate or coordinate:Translate(UTILS.NMToMeters(5), self.group:GetHeading(), true) local wp0=coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true) local wp9=Coordinate:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, "Refuel") - + self:Route({wp0, wp9}) - + end --- On after "Refueled" event. @@ -2157,10 +2157,10 @@ function FLIGHTGROUP:onafterRefueled(From, Event, To) local text=string.format("Flight group finished refuelling") MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) - + -- Check if flight is done. self:_CheckGroupDone(1) - + end @@ -2173,47 +2173,47 @@ function FLIGHTGROUP:onafterHolding(From, Event, To) -- Set holding flag to 0 (just in case). self.flaghold:Set(0) - + -- Holding time stamp. - self.Tholding=timer.getAbsTime() + self.Tholding=timer.getAbsTime() local text=string.format("Flight group %s is HOLDING now", self.groupname) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) - + -- Add flight to waiting/holding queue. if self.flightcontrol then - + -- Set flight status to holding self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.HOLDING) - + if not self.ai then self:_UpdateMenu() end - + elseif self.airboss then - + if self.ishelo then - + local carrierpos=self.airboss:GetCoordinate() local carrierheading=self.airboss:GetHeading() - + local Distance=UTILS.NMToMeters(5) local Angle=carrierheading+90 local altitude=math.random(12, 25)*100 local oc=carrierpos:Translate(Distance,Angle):SetAltitude(altitude, true) - + -- Orbit until flaghold=1 (true) but max 5 min if no FC is giving the landing clearance. local TaskOrbit=self.group:TaskOrbit(oc, nil, UTILS.KnotsToMps(50)) local TaskLand=self.group:TaskCondition(nil, self.flaghold.UserFlagName, 1) local TaskHold=self.group:TaskControlled(TaskOrbit, TaskLand) local TaskKlar=self.group:TaskFunction("FLIGHTGROUP._ClearedToLand", self) -- Once the holding flag becomes true, set trigger FLIGHTLANDING, i.e. set flight STATUS to LANDING. - + local DCSTask=self.group:TaskCombo({TaskOrbit, TaskHold, TaskKlar}) - + self:SetTask(DCSTask) end - + end end @@ -2227,21 +2227,21 @@ end function FLIGHTGROUP:onafterEngageTargets(From, Event, To, TargetUnitSet) local DCSTasks={} - + for _,_unit in paris(TargetUnitSet:GetSet()) do local unit=_unit --Wrapper.Unit#UNIT local task=self.group:TaskAttackUnit(unit, true) table.insert(DCSTasks) end - + -- Task combo. local DCSTask=self.group:TaskCombo(DCSTasks) - + --TODO needs a task function that calls EngageDone or so event and updates the route again. - + -- Lets try if pushtask actually leaves the remaining tasks untouched. self:SetTask(DCSTask) - + end --- On before "LandAt" event. Check we have a helo group. @@ -2266,18 +2266,18 @@ function FLIGHTGROUP:onafterLandAt(From, Event, To, Coordinate, Duration) -- Duration. Duration=Duration or 600 - + Coordinate=Coordinate or self:GetCoordinate() local DCStask=self.group:TaskLandAtVec2(Coordinate:GetVec2(), Duration) - + local Task=self:NewTaskScheduled(DCStask, 1, "Task_Land_At", 0) -- Add task with high priority. --self:AddTask(task, 1, "Task_Land_At", 0) - + self:TaskExecute(Task) - + end --- On after "FuelLow" event. @@ -2291,55 +2291,55 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) local text=string.format("Low fuel for flight group %s", self.groupname) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) - + -- Set switch to true. self.fuellow=true -- Back to destination or home. local airbase=self.destbase or self.homebase - + if self.airwing then - + -- Get closest tanker from airwing that can refuel this flight. local tanker=self.airwing:GetTankerForFlight(self) - + if tanker then - + -- Send flight to tanker with refueling task. self:Refuel(tanker.flightgroup:GetCoordinate()) - + else - + if airbase and self.fuellowrtb then self:RTB(airbase) --TODO: RTZ end - + end - + else - + if self.fuellowrefuel and self.refueltype then - + local tanker=self:FindNearestTanker(50) - + if tanker then - + self:I(self.lid..string.format("Send to refuel at tanker %s", tanker:GetName())) - + self:Refuel() - - return + + return end end - + if airbase and self.fuellowrtb then self:RTB(airbase) --TODO: RTZ end - + end - + end --- On after "FuelCritical" event. @@ -2353,13 +2353,13 @@ function FLIGHTGROUP:onafterFuelCritical(From, Event, To) local text=string.format("Critical fuel for flight group %s", self.groupname) MESSAGE:New(text, 30, "DEBUG"):ToAllIf(self.Debug) self:I(self.lid..text) - + -- Set switch to true. self.fuelcritical=true -- Airbase. local airbase=self.destbase or self.homebase - + if airbase and self.fuelcriticalrtb and not self:IsGoing4Fuel() then self:RTB(airbase) --TODO: RTZ @@ -2375,7 +2375,7 @@ function FLIGHTGROUP:onafterStop(From, Event, To) -- Check if group is still alive. if self:IsAlive() then - + -- Set element parking spot to FREE (after arrived for example). if self.flightcontrol then for _,_element in pairs(self.elements) do @@ -2383,7 +2383,7 @@ function FLIGHTGROUP:onafterStop(From, Event, To) self:_SetElementParkingFree(element) end end - + -- Destroy group. No event is generated. self.group:Destroy(false) end @@ -2398,9 +2398,9 @@ function FLIGHTGROUP:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Ejection) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.RemoveUnit) - + self.CallScheduler:Clear() - + _DATABASE.FLIGHTGROUPS[self.groupname]=nil self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") @@ -2477,44 +2477,44 @@ function FLIGHTGROUP:_InitGroup() -- Helo group. self.ishelo=self.group:IsHelicopter() - + -- Is (template) group uncontrolled. self.isUncontrolled=self.template.uncontrolled - + -- Is (template) group late activated. self.isLateActivated=self.template.lateActivation - + -- Max speed in km/h. self.speedmax=self.group:GetSpeedMax() - + -- Cruise speed limit 350 kts for fixed and 80 knots for rotary wings. local speedCruiseLimit=self.ishelo and UTILS.KnotsToKmph(80) or UTILS.KnotsToKmph(350) - + -- Cruise speed: 70% of max speed but within limit. self.speedCruise=math.min(self.speedmax*0.7, speedCruiseLimit) - + -- Group ammo. self.ammo=self:GetAmmoTot() - + -- Initial fuel mass. -- TODO: this is a unit property! self.fuelmass=0 - + self.traveldist=0 self.traveltime=timer.getAbsTime() self.position=self:GetCoordinate() - + -- Radio parameters from template. self.radioOn=self.template.communication self.radioFreq=self.template.frequency self.radioModu=self.template.modulation - + -- If not set by the use explicitly yet, we take the template values as defaults. if not self.radioFreqDefault then self.radioFreqDefault=self.radioFreq self.radioModuDefault=self.radioModu end - + -- Set default formation. if not self.formationDefault then if self.ishelo then @@ -2523,38 +2523,38 @@ function FLIGHTGROUP:_InitGroup() self.formationDefault=ENUMS.Formation.FixedWing.EchelonLeft.Group end end - + self.ai=not self:_IsHuman(self.group) - + if not self.ai then self.menu=self.menu or {} self.menu.atc=self.menu.atc or {} - self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") + self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") end - + self:SwitchFormation(self.formationDefault) - + -- Add elemets. for _,unit in pairs(self.group:GetUnits()) do local element=self:AddElementByName(unit:GetName()) - end - + end + -- Get first unit. This is used to extract other parameters. local unit=self.group:GetUnit(1) - + if unit then - + self.rangemax=unit:GetRange() - + self.descriptors=unit:GetDesc() - + self.actype=unit:GetTypeName() - + self.ceiling=self.descriptors.Hmax - + self.tankertype=select(2, unit:IsTanker()) self.refueltype=select(2, unit:IsRefuelable()) - + -- Debug info. local text=string.format("Initialized Flight Group %s:\n", self.groupname) text=text..string.format("AC type = %s\n", self.actype) @@ -2576,14 +2576,14 @@ function FLIGHTGROUP:_InitGroup() text=text..string.format("Start Air = %s\n", tostring(self:IsTakeoffAir())) text=text..string.format("Start Cold = %s\n", tostring(self:IsTakeoffCold())) text=text..string.format("Start Hot = %s\n", tostring(self:IsTakeoffHot())) - text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) + text=text..string.format("Start Rwy = %s\n", tostring(self:IsTakeoffRunway())) self:I(self.lid..text) - + -- Init done. self.groupinitialized=true - + end - + return self end @@ -2603,7 +2603,7 @@ function FLIGHTGROUP:AddElementByName(unitname) element.unit=unit element.status=OPSGROUP.ElementStatus.INUTERO element.group=unit:GetGroup() - + element.modex=element.unit:GetTemplate().onboard_num element.skill=element.unit:GetTemplate().skill element.pylons=element.unit:GetTemplatePylons() @@ -2614,21 +2614,21 @@ function FLIGHTGROUP:AddElementByName(unitname) element.categoryname=element.unit:GetCategoryName() element.callsign=element.unit:GetCallsign() element.size=element.unit:GetObjectSize() - + if element.skill=="Client" or element.skill=="Player" then element.ai=false element.client=CLIENT:FindByName(unitname) else element.ai=true end - + local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d %%), category=%d, categoryname=%s, callsign=%s, ai=%s", element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel, element.category, element.categoryname, element.callsign, tostring(element.ai)) self:I(self.lid..text) -- Add element to table. table.insert(self.elements, element) - + if unit:IsAlive() then self:ElementSpawned(element) end @@ -2646,24 +2646,24 @@ end function FLIGHTGROUP:GetHomebaseFromWaypoints() local wp=self:GetWaypoint(1) - + if wp then - - if wp and wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea - or wp.action==COORDINATE.WaypointAction.FromParkingAreaHot + + if wp and wp.action and wp.action==COORDINATE.WaypointAction.FromParkingArea + or wp.action==COORDINATE.WaypointAction.FromParkingAreaHot or wp.action==COORDINATE.WaypointAction.FromRunway then - + -- Get airbase ID depending on airbase category. local airbaseID=wp.airdromeId or wp.helipadId - + local airbase=AIRBASE:FindByID(airbaseID) - - return airbase + + return airbase end - + --TODO: Handle case where e.g. only one WP but that is not landing. --TODO: Probably other cases need to be taken care of. - + end return nil @@ -2681,24 +2681,24 @@ function FLIGHTGROUP:FindNearestAirbase(Radius) local airbase=nil --Wrapper.Airbase#AIRBASE for _,_airbase in pairs(AIRBASE.GetAllAirbases()) do local ab=_airbase --Wrapper.Airbase#AIRBASE - + local coalitionAB=ab:GetCoalition() - + if coalitionAB==self:GetCoalition() or coalitionAB==coalition.side.NEUTRAL then - + if airbase then local d=ab:GetCoordinate():Get2DDistance(coord) - + if d %s Destination", #self.waypoints, self.homebase and self.homebase:GetName() or "unknown", self.destbase and self.destbase:GetName() or "uknown")) - + -- Update route. if #self.waypoints>0 then - + -- Check if only 1 wp? if #self.waypoints==1 then self.passedfinalwp=true end - + -- Update route (when airborne). --self:_CheckGroupDone(1) self:__UpdateRoute(-1) @@ -2998,11 +2998,11 @@ function FLIGHTGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute) -- Waypoint number. Default is at the end. wpnumber=wpnumber or #self.waypoints+1 - + if wpnumber>self.currentwp then self.passedfinalwp=false end - + -- Speed in knots. speed=speed or 350 @@ -3011,20 +3011,20 @@ function FLIGHTGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute) -- Create air waypoint. local wp=coordinate:WaypointAir(COORDINATE.WaypointAltType.BARO, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, speedkmh, true, nil, {}, string.format("Added Waypoint #%d", wpnumber)) - + -- Add to table. table.insert(self.waypoints, wpnumber, wp) - + -- Debug info. self:T(self.lid..string.format("Adding AIR waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints)) - + -- Shift all waypoint tasks after the inserted waypoint. for _,_task in pairs(self.taskqueue) do local task=_task --#FLIGHTGROUP.Task if task.type==OPSGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then task.waypoint=task.waypoint+1 end - end + end -- Shift all mission waypoints after the inserted waypoint. for _,_mission in pairs(self.missionqueue) do @@ -3032,20 +3032,20 @@ function FLIGHTGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute) -- Get mission waypoint index. local wpidx=mission:GetGroupWaypointIndex(self) - + -- Increase number if this waypoint lies in the future. if wpidx and wpidx>=wpnumber then mission:SetGroupWaypointIndex(self, wpidx+1) - end - + end + end - + -- Update route. if updateroute==nil or updateroute==true then self:_CheckGroupDone(1) --self:__UpdateRoute(-1) end - + return wpnumber end @@ -3079,17 +3079,17 @@ function FLIGHTGROUP:_SetElementParkingAt(Element, Spot) -- Element is parking here. Element.parking=Spot - + if Spot then - - self:I(self.lid..string.format("Element %s is parking on spot %d", Element.name, Spot.TerminalID)) - + + self:I(self.lid..string.format("Element %s is parking on spot %d", Element.name, Spot.TerminalID)) + if self.flightcontrol then - + -- Set parking spot to OCCUPIED. self.flightcontrol:SetParkingOccupied(Element.parking, Element.name) end - + end end @@ -3105,12 +3105,12 @@ function FLIGHTGROUP:_SetElementParkingFree(Element) if self.flightcontrol then self.flightcontrol:SetParkingFree(Element.parking) end - + -- Not parking any more. Element.parking=nil end - + end --- Get onboard number. @@ -3142,10 +3142,10 @@ end -- @param Wrapper.Unit#UNIT unit Aircraft unit. -- @return #boolean If true, human player inside the unit. function FLIGHTGROUP:_IsHumanUnit(unit) - + -- Get player unit or nil if no player unit. local playerunit=self:_GetPlayerUnitAndName(unit:GetName()) - + if playerunit then return true else @@ -3161,7 +3161,7 @@ function FLIGHTGROUP:_IsHuman(group) -- Get all units of the group. local units=group:GetUnits() - + -- Loop over all units. for _,_unit in pairs(units) do -- Check if unit is human. @@ -3174,7 +3174,7 @@ function FLIGHTGROUP:_IsHuman(group) return false end ---- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. +--- Returns the unit of a player and the player name. If the unit does not belong to a player, nil is returned. -- @param #FLIGHTGROUP self -- @param #string _unitName Name of the player unit. -- @return Wrapper.Unit#UNIT Unit of player or nil. @@ -3183,23 +3183,23 @@ function FLIGHTGROUP:_GetPlayerUnitAndName(_unitName) self:F2(_unitName) if _unitName ~= nil then - + -- Get DCS unit from its name. local DCSunit=Unit.getByName(_unitName) - + if DCSunit then - + local playername=DCSunit:getPlayerName() local unit=UNIT:Find(DCSunit) - + if DCSunit and unit and playername then return unit, playername end - + end - + end - + -- Return nil if we could not find a player. return nil,nil end @@ -3215,19 +3215,19 @@ function FLIGHTGROUP:GetParkingSpot(element, maxdist, airbase) local coord=element.unit:GetCoordinate() airbase=airbase or coord:GetClosestAirbase(nil, self:GetCoalition()) - + -- TODO: replace by airbase.parking if AIRBASE is updated. local parking=airbase:GetParkingSpotsTable() - + local spot=nil --Wrapper.Airbase#AIRBASE.ParkingSpot local dist=nil - local distmin=math.huge + local distmin=math.huge for _,_parking in pairs(parking) do local parking=_parking --Wrapper.Airbase#AIRBASE.ParkingSpot dist=coord:Get2DDistance(parking.Coordinate) if dist Date: Mon, 29 Jun 2020 21:08:57 +0200 Subject: [PATCH 2/2] blanks --- Moose Development/Moose/Core/Database.lua | 289 +++++++------- Moose Development/Moose/Wrapper/Airbase.lua | 407 ++++++++++---------- 2 files changed, 344 insertions(+), 352 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index e634bba13..3b89132fc 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1,9 +1,9 @@ ---- **Core** - Manages several databases containing templates, mission objects, and mission information. --- +--- **Core** - Manages several databases containing templates, mission objects, and mission information. +-- -- === --- +-- -- ## Features: --- +-- -- * During mission startup, scan the mission environment, and create / instantiate intelligently the different objects as defined within the mission. -- * Manage database of DCS Group templates (as modelled using the mission editor). -- - Group templates. @@ -20,14 +20,14 @@ -- * Manage database of hits to units and statics. -- * Manage database of destroys of units and statics. -- * Manage database of @{Core.Zone#ZONE_BASE} objects. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- ### Contributions: --- +-- ### Contributions: +-- -- === --- +-- -- @module Core.Database -- @image Core_Database.JPG @@ -36,9 +36,9 @@ -- @extends Core.Base#BASE --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. --- +-- -- Mission designers can use the DATABASE class to refer to: --- +-- -- * STATICS -- * UNITS -- * GROUPS @@ -47,12 +47,12 @@ -- * PLAYERSJOINED -- * PLAYERS -- * CARGOS --- +-- -- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. --- +-- -- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission. -- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. --- +-- -- @field #DATABASE DATABASE = { ClassName = "DATABASE", @@ -116,7 +116,7 @@ function DATABASE:New() local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE self:SetEventPriority( 1 ) - + self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) @@ -126,11 +126,11 @@ function DATABASE:New() self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) - + -- Follow alive players and clients --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) - + self:_RegisterTemplates() self:_RegisterGroupsAndUnits() self:_RegisterClients() @@ -139,16 +139,16 @@ function DATABASE:New() self:_RegisterAirbases() self.UNITS_Position = 0 - + --- @param #DATABASE self local function CheckPlayers( self ) - + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL )} for CoalitionId, CoalitionData in pairs( CoalitionsData ) do --self:E( { "CoalitionData:", CoalitionData } ) for UnitId, UnitData in pairs( CoalitionData ) do if UnitData and UnitData:isExist() then - + local UnitName = UnitData:getName() local PlayerName = UnitData:getPlayerName() local PlayerUnit = UNIT:Find( UnitData ) @@ -167,10 +167,10 @@ function DATABASE:New() end end end - + --self:E( "Scheduling" ) --PlayerCheckSchedule = SCHEDULER:New( nil, CheckPlayers, { self }, 1, 1 ) - + return self end @@ -193,10 +193,10 @@ function DATABASE:AddUnit( DCSUnitName ) self:T( { "Add UNIT:", DCSUnitName } ) local UnitRegister = UNIT:Register( DCSUnitName ) self.UNITS[DCSUnitName] = UNIT:Register( DCSUnitName ) - + table.insert( self.UNITS_Index, DCSUnitName ) end - + return self.UNITS[DCSUnitName] end @@ -205,7 +205,7 @@ end -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) - self.UNITS[DCSUnitName] = nil + self.UNITS[DCSUnitName] = nil end --- Adds a Static based on the Static Name in the DATABASE. @@ -216,7 +216,7 @@ function DATABASE:AddStatic( DCSStaticName ) self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) return self.STATICS[DCSStaticName] end - + return nil end @@ -225,7 +225,7 @@ end -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) - --self.STATICS[DCSStaticName] = nil + --self.STATICS[DCSStaticName] = nil end --- Finds a STATIC based on the StaticName. @@ -257,7 +257,7 @@ function DATABASE:AddAirbase( AirbaseName ) if not self.AIRBASES[AirbaseName] then self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName ) end - + return self.AIRBASES[AirbaseName] end @@ -267,7 +267,7 @@ end -- @param #string AirbaseName The name of the airbase function DATABASE:DeleteAirbase( AirbaseName ) - self.AIRBASES[AirbaseName] = nil + self.AIRBASES[AirbaseName] = nil end --- Finds an AIRBASE based on the AirbaseName. @@ -288,29 +288,29 @@ do -- Zones -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZone( ZoneName ) - + local ZoneFound = self.ZONES[ZoneName] return ZoneFound end - + --- Adds a @{Zone} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @param Core.Zone#ZONE_BASE Zone The zone. function DATABASE:AddZone( ZoneName, Zone ) - + if not self.ZONES[ZoneName] then self.ZONES[ZoneName] = Zone end end - - + + --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteZone( ZoneName ) - - self.ZONES[ZoneName] = nil + + self.ZONES[ZoneName] = nil end @@ -327,20 +327,20 @@ do -- Zones self.ZONENAMES[ZoneName] = ZoneName self:AddZone( ZoneName, Zone ) end - + for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) - + self:I( { "Register ZONE_POLYGON:", Name = ZoneName } ) local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) self.ZONENAMES[ZoneName] = ZoneName self:AddZone( ZoneName, Zone_Polygon ) end end - + end @@ -353,29 +353,29 @@ do -- Zone_Goal -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZoneGoal( ZoneName ) - + local ZoneFound = self.ZONES_GOAL[ZoneName] return ZoneFound end - + --- Adds a @{Zone} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @param Core.Zone#ZONE_BASE Zone The zone. function DATABASE:AddZoneGoal( ZoneName, Zone ) - + if not self.ZONES_GOAL[ZoneName] then self.ZONES_GOAL[ZoneName] = Zone end end - - + + --- Deletes a @{Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteZoneGoal( ZoneName ) - - self.ZONES_GOAL[ZoneName] = nil + + self.ZONES_GOAL[ZoneName] = nil end end -- Zone_Goal @@ -385,31 +385,31 @@ do -- cargo -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:AddCargo( Cargo ) - + if not self.CARGOS[Cargo.Name] then self.CARGOS[Cargo.Name] = Cargo end end - - + + --- Deletes a Cargo from the DATABASE based on the Cargo Name. -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:DeleteCargo( CargoName ) - - self.CARGOS[CargoName] = nil + + self.CARGOS[CargoName] = nil end - + --- Finds an CARGO based on the CargoName. -- @param #DATABASE self -- @param #string CargoName -- @return Wrapper.Cargo#CARGO The found CARGO. function DATABASE:FindCargo( CargoName ) - + local CargoFound = self.CARGOS[CargoName] return CargoFound end - + --- Checks if the Template name has a #CARGO tag. -- If yes, the group is a cargo. -- @param #DATABASE self @@ -418,10 +418,10 @@ do -- cargo function DATABASE:IsCargo( TemplateName ) TemplateName = env.getValueDictByKey( TemplateName ) - + local Cargo = TemplateName:match( "#(CARGO)" ) - return Cargo and Cargo == "CARGO" + return Cargo and Cargo == "CARGO" end --- Private method that registers new Static Templates within the DATABASE Object. @@ -430,7 +430,7 @@ do -- cargo function DATABASE:_RegisterCargos() local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug! - + for CargoGroupName, CargoGroup in pairs( Groups ) do self:I( { Cargo = CargoGroupName } ) if self:IsCargo( CargoGroupName ) then @@ -443,12 +443,12 @@ do -- cargo local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) - + self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) end end - + for CargoStaticName, CargoStatic in pairs( self.STATICS ) do if self:IsCargo( CargoStaticName ) then local CargoInfo = CargoStaticName:match("#CARGO(.*)") @@ -459,7 +459,7 @@ do -- cargo local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) - + if Category == "SLING" then self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) @@ -471,7 +471,7 @@ do -- cargo end end end - + end end -- cargo @@ -517,9 +517,9 @@ function DATABASE:AddGroup( GroupName ) if not self.GROUPS[GroupName] then self:T( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) - end - - return self.GROUPS[GroupName] + end + + return self.GROUPS[GroupName] end --- Adds a player based on the Player Name in the DATABASE. @@ -621,7 +621,7 @@ function DATABASE:Spawn( SpawnTemplate ) for UnitID, UnitData in pairs( SpawnTemplate.units ) do self:AddUnit( UnitData.name ) end - + return SpawnGroup end @@ -653,21 +653,21 @@ end function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name ) - + if not self.Templates.Groups[GroupTemplateName] then self.Templates.Groups[GroupTemplateName] = {} self.Templates.Groups[GroupTemplateName].Status = nil end - + -- Delete the spans from the route, it is not needed and takes memory. - if GroupTemplate.route and GroupTemplate.route.spans then + if GroupTemplate.route and GroupTemplate.route.spans then GroupTemplate.route.spans = nil end - + GroupTemplate.CategoryID = CategoryID GroupTemplate.CoalitionID = CoalitionSide GroupTemplate.CountryID = CountryID - + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName self.Templates.Groups[GroupTemplateName].Template = GroupTemplate self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId @@ -682,7 +682,7 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) - + self.Templates.Units[UnitTemplate.name] = {} self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name self.Templates.Units[UnitTemplate.name].Template = UnitTemplate @@ -700,8 +700,8 @@ function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, Category self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - - UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName + + UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName end self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, @@ -730,13 +730,13 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) local StaticTemplateName = env.getValueDictByKey(StaticTemplate.name) - + self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} - + StaticTemplate.CategoryID = CategoryID StaticTemplate.CoalitionID = CoalitionID StaticTemplate.CountryID = CountryID - + self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateName self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1] @@ -747,12 +747,12 @@ function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, Category self:I( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, - Country = self.Templates.Statics[StaticTemplateName].CountryID + Country = self.Templates.Statics[StaticTemplateName].CountryID } ) - + self:AddStatic( StaticTemplateName ) - + end @@ -820,7 +820,7 @@ function DATABASE:_RegisterPlayers() end end end - + return self end @@ -836,12 +836,12 @@ function DATABASE:_RegisterGroupsAndUnits() if DCSGroup:isExist() then local DCSGroupName = DCSGroup:getName() - + self:I( { "Register Group:", DCSGroupName } ) self:AddGroup( DCSGroupName ) for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do - + local DCSUnitName = DCSUnit:getName() self:I( { "Register Unit:", DCSUnitName } ) self:AddUnit( DCSUnitName ) @@ -849,10 +849,10 @@ function DATABASE:_RegisterGroupsAndUnits() else self:E( { "Group does not exist: ", DCSGroup } ) end - + end end - + self:T("Groups:") for GroupName, Group in pairs( self.GROUPS ) do self:T( { "Group:", GroupName } ) @@ -870,7 +870,7 @@ function DATABASE:_RegisterClients() self:T( { "Register Client:", ClientName } ) self:AddClient( ClientName ) end - + return self end @@ -884,7 +884,7 @@ function DATABASE:_RegisterStatics() if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - + self:T( { "Register Static:", DCSStaticName } ) self:AddStatic( DCSStaticName ) else @@ -914,14 +914,14 @@ function DATABASE:_RegisterAirbases() for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do local DCSAirbaseName = DCSAirbase:getName() - + -- This gives the incorrect value to be inserted into the airdromeID for DCS 2.5.6! local airbaseID=DCSAirbase:getID() - + local airbase=self:AddAirbase( DCSAirbaseName ) - - self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) - end + + self:I(string.format("Register Airbase: %s, getID=%d, GetID=%d (unique=%d)", DCSAirbaseName, DCSAirbase:getID(), airbase:GetID(), airbase:GetID(true))) + end return self end @@ -937,7 +937,7 @@ function DATABASE:_EventOnBirth( Event ) if Event.IniDCSUnit then if Event.IniObjectCategory == 3 then - self:AddStatic( Event.IniDCSUnitName ) + self:AddStatic( Event.IniDCSUnitName ) else if Event.IniObjectCategory == 1 then self:AddUnit( Event.IniDCSUnitName ) @@ -979,7 +979,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) if Event.IniObjectCategory == 3 then if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) - end + end else if Event.IniObjectCategory == 1 then if self.UNITS[Event.IniDCSUnitName] then @@ -988,7 +988,7 @@ function DATABASE:_EventOnDeadOrCrash( Event ) end end end - + self:AccountDestroys( Event ) end @@ -1042,7 +1042,7 @@ end -- @return #DATABASE self function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) self:F2( arg ) - + local function CoRoutine() local Count = 0 for ObjectID, Object in pairs( Set ) do @@ -1051,20 +1051,20 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) Count = Count + 1 -- if Count % 100 == 0 then -- coroutine.yield( false ) --- end +-- end end return true end - + -- local co = coroutine.create( CoRoutine ) local co = CoRoutine - + local function Schedule() - + -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) - + if status == false then error( res ) end @@ -1079,7 +1079,7 @@ function DATABASE:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) --local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) Schedule() - + return self end @@ -1090,7 +1090,7 @@ end -- @return #DATABASE self function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1 self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS ) return self @@ -1103,7 +1103,7 @@ end -- @return #DATABASE self function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) return self @@ -1116,7 +1116,7 @@ end -- @return #DATABASE self function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.GROUPS ) return self @@ -1129,9 +1129,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERS ) - + return self end @@ -1142,9 +1142,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayerJoined( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERSJOINED ) - + return self end @@ -1154,9 +1154,9 @@ end -- @return #DATABASE self function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERUNITS ) - + return self end @@ -1167,7 +1167,7 @@ end -- @return #DATABASE self function DATABASE:ForEachClient( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self.CLIENTS ) return self @@ -1179,7 +1179,7 @@ end -- @return #DATABASE self function DATABASE:ForEachCargo( IteratorFunction, ... ) self:F2( arg ) - + self:ForEach( IteratorFunction, arg, self.CARGOS ) return self @@ -1306,7 +1306,7 @@ function DATABASE:_RegisterTemplates() if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then --self.Units[coa_name] = {} - + local CoalitionSide = coalition.side[string.upper(CoalitionName)] if CoalitionName=="red" then CoalitionSide=coalition.side.RED @@ -1339,10 +1339,10 @@ function DATABASE:_RegisterTemplates() local CountryName = string.upper(cntry_data.name) local CountryID = cntry_data.id - + self.COUNTRY_ID[CountryName] = CountryID self.COUNTRY_NAME[CountryID] = CountryName - + --self.Units[coa_name][countryName] = {} --self.Units[coa_name][countryName]["countryId"] = cntry_data.id @@ -1361,18 +1361,18 @@ function DATABASE:_RegisterTemplates() for group_num, Template in pairs(obj_type_data.group) do if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group - self:_RegisterGroupTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID + self:_RegisterGroupTemplate( + Template, + CoalitionSide, + _DATABASECategory[string.lower(CategoryName)], + CountryID ) else - self:_RegisterStaticTemplate( - Template, - CoalitionSide, - _DATABASECategory[string.lower(CategoryName)], - CountryID + self:_RegisterStaticTemplate( + Template, + CoalitionSide, + _DATABASECategory[string.lower(CategoryName)], + CountryID ) end --if GroupTemplate and GroupTemplate.units then end --for group_num, GroupTemplate in pairs(obj_type_data.group) do @@ -1393,35 +1393,35 @@ end -- @param Core.Event#EVENTDATA Event function DATABASE:AccountHits( Event ) self:F( { Event } ) - + if Event.IniPlayerName ~= nil then -- It is a player that is hitting something self:T( "Hitting Something" ) - + -- What is he hitting? if Event.TgtCategory then - + -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] - + Hit.Players = Hit.Players or {} Hit.Players[Event.IniPlayerName] = true end end - + -- It is a weapon initiated by a player, that is hitting something -- This seems to occur only with scenery and static objects. - if Event.WeaponPlayerName ~= nil then + if Event.WeaponPlayerName ~= nil then self:T( "Hitting Scenery" ) - + -- What is he hitting? if Event.TgtCategory then - + if Event.WeaponCoalition then -- A coalition object was hit, probably a static. -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] - + Hit.Players = Hit.Players or {} Hit.Players[Event.WeaponPlayerName] = true else -- A scenery object was hit. @@ -1429,13 +1429,13 @@ end end end end - + --- Account the destroys. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:AccountDestroys( Event ) self:F( { Event } ) - + local TargetUnit = nil local TargetGroup = nil local TargetUnitName = "" @@ -1447,26 +1447,26 @@ end local TargetUnitCoalition = nil local TargetUnitCategory = nil local TargetUnitType = nil - + if Event.IniDCSUnit then - + TargetUnit = Event.IniUnit TargetUnitName = Event.IniDCSUnitName TargetGroup = Event.IniDCSGroup TargetGroupName = Event.IniDCSGroupName TargetPlayerName = Event.IniPlayerName - + TargetCoalition = Event.IniCoalition --TargetCategory = TargetUnit:getCategory() --TargetCategory = TargetUnit:getDesc().category -- Workaround TargetCategory = Event.IniCategory TargetType = Event.IniTypeName - + TargetUnitType = TargetType - + self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) end - + local Destroyed = false -- What is the player destroying? @@ -1475,8 +1475,3 @@ end self.DESTROYS[Event.IniUnitName] = true end end - - - - - diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 55c78a30c..9482c97dd 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1,13 +1,13 @@ --- **Wrapper** -- AIRBASE is a wrapper class to handle the DCS Airbase objects. --- +-- -- === --- +-- -- ### Author: **FlightControl** --- +-- -- ### Contributions: **funkyfranky** --- +-- -- === --- +-- -- @module Wrapper.Airbase -- @image Wrapper_Airbase.JPG @@ -19,40 +19,40 @@ -- @extends Wrapper.Positionable#POSITIONABLE --- Wrapper class to handle the DCS Airbase objects: --- +-- -- * Support all DCS Airbase APIs. -- * Enhance with Airbase specific APIs not in the DCS Airbase API set. --- +-- -- ## AIRBASE reference methods --- +-- -- For each DCS Airbase object alive within a running mission, a AIRBASE wrapper object (instance) will be created within the _@{DATABASE} object. -- This is done at the beginning of the mission (when the mission starts). --- +-- -- The AIRBASE class **does not contain a :New()** method, rather it provides **:Find()** methods to retrieve the object reference -- using the DCS Airbase or the DCS AirbaseName. --- --- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. +-- +-- Another thing to know is that AIRBASE objects do not "contain" the DCS Airbase object. -- The AIRBASE methods will reference the DCS Airbase object by name when it is needed during API execution. -- If the DCS Airbase object does not exist or is nil, the AIRBASE methods will return nil and log an exception in the DCS.log file. --- +-- -- The AIRBASE class provides the following functions to retrieve quickly the relevant AIRBASE instance: --- +-- -- * @{#AIRBASE.Find}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase object. -- * @{#AIRBASE.FindByName}(): Find a AIRBASE instance from the _DATABASE object using a DCS Airbase name. --- +-- -- IMPORTANT: ONE SHOULD NEVER SANATIZE these AIRBASE OBJECT REFERENCES! (make the AIRBASE object references nil). --- +-- -- ## DCS Airbase APIs --- +-- -- The DCS Airbase APIs are used extensively within MOOSE. The AIRBASE class has for each DCS Airbase API a corresponding method. -- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call, -- the first letter of the method is also capitalized. So, by example, the DCS Airbase method @{DCSWrapper.Airbase#Airbase.getName}() -- is implemented in the AIRBASE class as @{#AIRBASE.GetName}(). --- +-- -- @field #AIRBASE AIRBASE AIRBASE = { ClassName="AIRBASE", - CategoryName = { + CategoryName = { [Airbase.Category.AIRDROME] = "Airdrome", [Airbase.Category.HELIPAD] = "Helipad", [Airbase.Category.SHIP] = "Ship", @@ -61,9 +61,9 @@ AIRBASE = { } --- Enumeration to identify the airbases in the Caucasus region. --- +-- -- These are all airbases of Caucasus: --- +-- -- * AIRBASE.Caucasus.Gelendzhik -- * AIRBASE.Caucasus.Krasnodar_Pashkovsky -- * AIRBASE.Caucasus.Sukhumi_Babushara @@ -85,7 +85,7 @@ AIRBASE = { -- * AIRBASE.Caucasus.Nalchik -- * AIRBASE.Caucasus.Mozdok -- * AIRBASE.Caucasus.Beslan --- +-- -- @field Caucasus AIRBASE.Caucasus = { ["Gelendzhik"] = "Gelendzhik", @@ -112,7 +112,7 @@ AIRBASE.Caucasus = { } --- These are all airbases of Nevada: --- +-- -- * AIRBASE.Nevada.Creech_AFB -- * AIRBASE.Nevada.Groom_Lake_AFB -- * AIRBASE.Nevada.McCarran_International_Airport @@ -130,7 +130,7 @@ AIRBASE.Caucasus = { -- * AIRBASE.Nevada.Pahute_Mesa_Airstrip -- * AIRBASE.Nevada.Tonopah_Airport -- * AIRBASE.Nevada.Tonopah_Test_Range_Airfield --- @field Nevada +-- @field Nevada AIRBASE.Nevada = { ["Creech_AFB"] = "Creech AFB", ["Groom_Lake_AFB"] = "Groom Lake AFB", @@ -152,7 +152,7 @@ AIRBASE.Nevada = { } --- These are all airbases of Normandy: --- +-- -- * AIRBASE.Normandy.Saint_Pierre_du_Mont -- * AIRBASE.Normandy.Lignerolles -- * AIRBASE.Normandy.Cretteville @@ -219,11 +219,11 @@ AIRBASE.Normandy = { ["Ford_AF"] = "Ford_AF", ["Goulet"] = "Goulet", ["Argentan"] = "Argentan", - ["Vrigny"] = "Vrigny", + ["Vrigny"] = "Vrigny", ["Essay"] = "Essay", ["Hauterive"] = "Hauterive", ["Barville"] = "Barville", - ["Conches"] = "Conches", + ["Conches"] = "Conches", } --- These are all airbases of the Persion Gulf Map: @@ -269,7 +269,7 @@ AIRBASE.PersianGulf = { ["Bandar_Abbas_Intl"] = "Bandar Abbas Intl", ["Bandar_Lengeh"] = "Bandar Lengeh", ["Bandar_e_Jask_airfield"] = "Bandar-e-Jask airfield", - ["Dubai_Intl"] = "Dubai Intl", + ["Dubai_Intl"] = "Dubai Intl", ["Fujairah_Intl"] = "Fujairah Intl", ["Havadarya"] = "Havadarya", ["Jiroft_Airport"] = "Jiroft Airport", @@ -301,7 +301,7 @@ AIRBASE.PersianGulf = { -- * AIRBASE.TheChannel.Lympne -- * AIRBASE.TheChannel.Detling -- * AIRBASE.TheChannel.High_Halden --- +-- -- @field TheChannel AIRBASE.TheChannel = { ["Abbeville_Drucat"] = "Abbeville Drucat", @@ -314,7 +314,7 @@ AIRBASE.TheChannel = { ["Detling"] = "Detling", ["High_Halden"] = "High Halden", } - + --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". -- @type AIRBASE.ParkingSpot -- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. @@ -324,11 +324,11 @@ AIRBASE.TheChannel = { -- @field #boolean Free This spot is currently free, i.e. there is no alive aircraft on it at the present moment. -- @field #number TerminalID0 Unknown what this means. If you know, please tell us! -- @field #number DistToRwy Distance to runway in meters. Currently bugged and giving the same number as the TerminalID. - + --- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking --- +-- -- Supported types are: --- +-- -- * AIRBASE.TerminalType.Runway = 16: Valid spawn points on runway. -- * AIRBASE.TerminalType.HelicopterOnly = 40: Special spots for Helicopers. -- * AIRBASE.TerminalType.Shelter = 68: Hardened Air Shelter. Currently only on Caucaus map. @@ -337,7 +337,7 @@ AIRBASE.TheChannel = { -- * AIRBASE.TerminalType.OpenMedOrBig = 176: Combines OpenMed and OpenBig spots. -- * AIRBASE.TerminalType.HelicopterUsable = 216: Combines HelicopterOnly, OpenMed and OpenBig. -- * AIRBASE.TerminalType.FighterAircraft = 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. --- +-- -- @type AIRBASE.TerminalType -- @field #number Runway 16: Valid spawn points on runway. -- @field #number HelicopterOnly 40: Special spots for Helicopers. @@ -367,7 +367,7 @@ AIRBASE.TerminalType = { -- @field Core.Point#COORDINATE endpoint End point of runway. -- Registration. - + --- Create a new AIRBASE from DCSAirbase. -- @param #AIRBASE self -- @param #string AirbaseName The name of the airbase. @@ -383,7 +383,7 @@ function AIRBASE:Register( AirbaseName ) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end - + return self end @@ -405,7 +405,7 @@ end -- @param #string AirbaseName The Airbase Name. -- @return #AIRBASE self function AIRBASE:FindByName( AirbaseName ) - + local AirbaseFound = _DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end @@ -415,18 +415,18 @@ end -- @param #number id Airbase ID. -- @return #AIRBASE self function AIRBASE:FindByID(id) - + for name,_airbase in pairs(_DATABASE.AIRBASES) do local airbase=_airbase --#AIRBASE - + local aid=tonumber(airbase:GetID(true)) - + if aid==id then return airbase end - + end - + return nil end @@ -435,11 +435,11 @@ end -- @return DCS#Airbase DCS airbase object. function AIRBASE:GetDCSObject() local DCSAirbase = Airbase.getByName( self.AirbaseName ) - + if DCSAirbase then return DCSAirbase end - + return nil end @@ -455,7 +455,7 @@ end -- @param #number category (Optional) Return only airbases of a certain category, e.g. Airbase.Category.FARP -- @return #table Table containing all airbase objects of the current map. function AIRBASE.GetAllAirbases(coalition, category) - + local airbases={} for _,_airbase in pairs(_DATABASE.AIRBASES) do local airbase=_airbase --#AIRBASE @@ -465,59 +465,59 @@ function AIRBASE.GetAllAirbases(coalition, category) end end end - + return airbases end --- Get ID of the airbase. -- @param #AIRBASE self --- @param #boolean unique (Optional) If true, ships will get a negative sign as the unit ID might be the same as an airbase ID. Default off! +-- @param #boolean unique (Optional) If true, ships will get a negative sign as the unit ID might be the same as an airbase ID. Default off! -- @return #number The airbase ID. function AIRBASE:GetID(unique) if self.AirbaseID then - + return unique and self.AirbaseID or math.abs(self.AirbaseID) - + else - + for DCSAirbaseId, DCSAirbase in ipairs(world.getAirbases()) do - + -- Get the airbase name. local AirbaseName = DCSAirbase:getName() - + -- This gives the incorrect value to be inserted into the airdromeID for DCS 2.5.6! local airbaseID=tonumber(DCSAirbase:getID()) - + local airbaseCategory=self:GetAirbaseCategory() - + --env.info(string.format("FF airbase=%s id=%s category=%s", tostring(AirbaseName), tostring(airbaseID), tostring(airbaseCategory))) - + -- No way AFIK to get the DCS version. So we check if the event exists. That should tell us if we are on DCS 2.5.6 or prior to that. --[[ if world.event.S_EVENT_KILL and world.event.S_EVENT_KILL>0 and airbaseCategory==Airbase.Category.AIRDROME then - + -- We have to take the key value of this loop! airbaseID=DCSAirbaseId - + -- Now another quirk: for Caucasus, we need to add 11 to the key value to get the correct ID. See https://forums.eagle.ru/showpost.php?p=4210774&postcount=11 if UTILS.GetDCSMap()==DCSMAP.Caucasus then - airbaseID=airbaseID+11 + airbaseID=airbaseID+11 end end ]] - + if AirbaseName==self.AirbaseName then if airbaseCategory==Airbase.Category.SHIP then -- Ships get a negative sign as their unit number might be the same as the ID of another airbase. return unique and -airbaseID or airbaseID else return airbaseID - end + end end - + end - + end return nil @@ -525,22 +525,22 @@ end --- Returns a table of parking data for a given airbase. If the optional parameter *available* is true only available parking will be returned, otherwise all parking at the base is returned. Term types have the following enumerated values: --- +-- -- * 16 : Valid spawn points on runway --- * 40 : Helicopter only spawn +-- * 40 : Helicopter only spawn -- * 68 : Hardened Air Shelter -- * 72 : Open/Shelter air airplane only -- * 104: Open air spawn --- +-- -- Note that only Caucuses will return 68 as it is the only map currently with hardened air shelters. -- 104 are also generally larger, but does not guarantee a large aircraft like the B-52 or a C-130 are capable of spawning there. --- +-- -- Table entries: --- +-- -- * Term_index is the id for the parking -- * vTerminal pos is its vec3 position in the world -- * fDistToRW is the distance to the take-off position for the active runway from the parking. --- +-- -- @param #AIRBASE self -- @param #boolean available If true, only available parking spots will be returned. -- @return #table Table with parking data. See https://wiki.hoggitworld.com/view/DCS_func_getParking @@ -549,13 +549,13 @@ function AIRBASE:GetParkingData(available) -- Get DCS airbase object. local DCSAirbase=self:GetDCSObject() - + -- Get parking data. local parkingdata=nil if DCSAirbase then parkingdata=DCSAirbase:getParking(available) end - + self:T2({parkingdata=parkingdata}) return parkingdata end @@ -568,27 +568,27 @@ function AIRBASE:GetParkingSpotsNumber(termtype) -- Get free parking spots data. local parkingdata=self:GetParkingData(false) - + local nspots=0 for _,parkingspot in pairs(parkingdata) do if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then nspots=nspots+1 end end - + return nspots end --- Get number of free parking spots at an airbase. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type. --- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. +-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #number Number of free parking spots at this airbase. function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC) -- Get free parking spots data. local parkingdata=self:GetParkingData(true) - + local nfree=0 for _,parkingspot in pairs(parkingdata) do -- Spots on runway are not counted unless explicitly requested. @@ -598,7 +598,7 @@ function AIRBASE:GetFreeParkingSpotsNumber(termtype, allowTOAC) end end end - + return nfree end @@ -611,7 +611,7 @@ function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC) -- Get free parking spots data. local parkingdata=self:GetParkingData(true) - + -- Put coordinates of free spots into table. local spots={} for _,parkingspot in pairs(parkingdata) do @@ -622,7 +622,7 @@ function AIRBASE:GetFreeParkingSpotsCoordinates(termtype, allowTOAC) end end end - + return spots end @@ -634,23 +634,23 @@ function AIRBASE:GetParkingSpotsCoordinates(termtype) -- Get all parking spots data. local parkingdata=self:GetParkingData(false) - + -- Put coordinates of free spots into table. local spots={} for _,parkingspot in ipairs(parkingdata) do - + -- Coordinates on runway are not returned unless explicitly requested. if AIRBASE._CheckTerminalType(parkingspot.Term_Type, termtype) then - + -- Get coordinate from Vec3 terminal position. local _coord=COORDINATE:NewFromVec3(parkingspot.vTerminalPos) - + -- Add to table. table.insert(spots, _coord) end - + end - + return spots end @@ -661,11 +661,11 @@ end -- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". function AIRBASE:GetParkingSpotsTable(termtype) - -- Get parking data of all spots (free or occupied) + -- Get parking data of all spots (free or occupied) local parkingdata=self:GetParkingData(false) -- Get parking data of all free spots. local parkingfree=self:GetParkingData(true) - + -- Function to ckeck if any parking spot is free. local function _isfree(_tocheck) for _,_spot in pairs(parkingfree) do @@ -675,7 +675,7 @@ function AIRBASE:GetParkingSpotsTable(termtype) end return false end - + -- Put coordinates of parking spots into table. local spots={} for _,_spot in pairs(parkingdata) do @@ -686,22 +686,22 @@ function AIRBASE:GetParkingSpotsTable(termtype) table.insert(spots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) end end - + self:T2({ spots = spots } ) - + return spots end --- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type. --- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. +-- @param #boolean allowTOAC If true, spots are considered free even though TO_AC is true. Default is off which is saver to avoid spawning aircraft on top of each other. Option might be enabled for FARPS and ships. -- @return #table Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) -- Get parking data of all free spots. local parkingfree=self:GetParkingData(true) - + -- Put coordinates of free spots into table. local freespots={} for _,_spot in pairs(parkingfree) do @@ -712,7 +712,7 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) end end end - + return freespots end @@ -725,10 +725,10 @@ function AIRBASE:GetParkingSpotData(TerminalID) -- Get parking data. local parkingdata=self:GetParkingSpotsTable() - + -- Debug output. self:T2({parkingdata=parkingdata}) - + for _,_spot in pairs(parkingdata) do local spot=_spot --#AIRBASE.ParkingSpot self:T({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) @@ -736,7 +736,7 @@ function AIRBASE:GetParkingSpotData(TerminalID) return spot end end - + self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID)) return nil end @@ -758,18 +758,18 @@ function AIRBASE:MarkParkingSpots(termtype, mark) -- Get airbase name. local airbasename=self:GetName() self:E(string.format("Parking spots at %s for termial type %s:", airbasename, tostring(termtype))) - + for _,_spot in pairs(parkingdata) do - + -- Mark text. local _text=string.format("Term Index=%d, Term Type=%d, Free=%s, TOAC=%s, Term ID0=%d, Dist2Rwy=%.1f m", _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) - + -- Create mark on the F10 map. if mark then _spot.Coordinate:MarkToAll(_text) end - + -- Info to DCS.log file. local _text=string.format("%s, Term Index=%3d, Term Type=%03d, Free=%5s, TOAC=%5s, Term ID0=%3d, Dist2Rwy=%.1f m", airbasename, _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy) @@ -787,7 +787,7 @@ end -- @param #boolean scanstatics (Optional) Scan for statics as obstacles. Default true. -- @param #boolean scanscenery (Optional) Scan for scenery as obstacles. Default false. Can cause problems with e.g. shelters. -- @param #boolean verysafe (Optional) If true, wait until an aircraft has taken off until the parking spot is considered to be free. Defaul false. --- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group. +-- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group. -- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) @@ -805,8 +805,8 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, end if verysafe==nil then verysafe=false - end - + end + -- Function calculating the overlap of two (square) objects. local function _overlap(object1, object2, dist) local pos1=object1 --Wrapper.Positionable#POSITIONABLE @@ -814,107 +814,107 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local r1=pos1:GetBoundingRadius() local r2=pos2:GetBoundingRadius() if r1 and r2 then - local safedist=(r1+r2)*1.1 + local safedist=(r1+r2)*1.1 local safe = (dist > safedist) self:T2(string.format("r1=%.1f r2=%.1f s=%.1f d=%.1f ==> safe=%s", r1, r2, safedist, dist, tostring(safe))) return safe else return true - end + end end - + -- Get airport name. local airport=self:GetName() - + -- Get parking spot data table. This contains free and "non-free" spots. -- Note that there are three major issues with the DCS getParking() function: -- 1. A spot is considered as NOT free until an aircraft that is present has finally taken off. This might be a bit long especiall at smaller airports. -- 2. A "free" spot does not take the aircraft size into accound. So if two big aircraft are spawned on spots next to each other, they might overlap and get destroyed. -- 3. The routine return a free spot, if there a static objects placed on the spot. parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) - + -- Get the aircraft size, i.e. it's longest side of x,z. local aircraft=group:GetUnit(1) local _aircraftsize, ax,ay,az=aircraft:GetObjectSize() - + -- Number of spots we are looking for. Note that, e.g. grouping can require a number different from the group size! local _nspots=nspots or group:GetSize() - + -- Debug info. self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at termial type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) - + -- Table of valid spots. local validspots={} local nvalid=0 - + -- Test other stuff if no parking spot is available. local _test=false if _test then return validspots end - + -- Mark all found obstacles on F10 map for debugging. local markobstacles=false - + -- Loop over all known parking spots for _,parkingspot in pairs(parkingdata) do - + -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID - + self:T2({_termid=_termid}) - + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then - + -- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off. if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then - + -- DCS getParking() routine returned that spot is not free. self:T(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC))) - + else - + -- Scan a radius of 50 meters around the spot. local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) - + -- Loop over objects within scan radius. local occupied=false - - -- Check all units. + + -- Check all units. for _,unit in pairs(_units) do local _coord=unit:GetCoordinate() - local _dist=_coord:Get2DDistance(_spot) + local _dist=_coord:Get2DDistance(_spot) local _safe=_overlap(aircraft, unit, _dist) - + if markobstacles then local l,x,y,z=unit:GetObjectSize() _coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:GetName(),x,y,z,l,_dist, _termid, tostring(_safe))) end - + if scanunits and not _safe then occupied=true - end + end end - + -- Check all statics. for _,static in pairs(_statics) do local _static=STATIC:Find(static) local _vec3=static:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) - local _dist=_coord:Get2DDistance(_spot) + local _dist=_coord:Get2DDistance(_spot) local _safe=_overlap(aircraft,_static,_dist) - + if markobstacles then local l,x,y,z=_static:GetObjectSize() _coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) end - + if scanstatics and not _safe then occupied=true - end + end end - + -- Check all scenery. for _,scenery in pairs(_sceneries) do local _scenery=SCENERY:Register(scenery:getTypeName(), scenery) @@ -922,17 +922,17 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local _coord=COORDINATE:NewFromVec3(_vec3) local _dist=_coord:Get2DDistance(_spot) local _safe=_overlap(aircraft,_scenery,_dist) - + if markobstacles then local l,x,y,z=scenery:GetObjectSize(scenery) _coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe))) end - + if scanscenery and not _safe then occupied=true - end + end end - + -- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot. for _,_takenspot in pairs(validspots) do local _dist=_takenspot.Coordinate:Get2DDistance(_spot) @@ -941,7 +941,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, occupied=true end end - + --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) if occupied then self:I(string.format("%s: Parking spot id %d occupied.", airport, _termid)) @@ -953,16 +953,16 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, nvalid=nvalid+1 self:I(string.format("%s: Parking spot id %d free. Nfree=%d/%d.", airport, _termid, nvalid,_nspots)) end - + end -- loop over units - + -- We found enough spots. if nvalid>=_nspots then return validspots end end -- check terminal type - end - + end + -- Retrun spots we found, even if there were not enough. return validspots end @@ -971,54 +971,54 @@ end -- @param #AIRBASE self -- @param Wrapper.Group#GROUP group Group to be checked. -- @param #number radius Radius around the spawn point to be checked. Default is 50 m. --- @param #boolean despawn If true, the group is destroyed. +-- @param #boolean despawn If true, the group is destroyed. -- @return #boolean True if group is within radius around spawn points on runway. function AIRBASE:CheckOnRunWay(group, radius, despawn) -- Default radius. radius=radius or 50 - + -- We only check at real airbases (not FARPS or ships). if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then return false end if group and group:IsAlive() then - + -- Debug. self:T(string.format("%s, checking if group %s is on runway?",self:GetName(), group:GetName())) - + -- Get coordinates on runway. local runwaypoints=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) - + -- Mark runway spawn points. --[[ for _i,_coord in pairs(runwaypoints) do _coord:MarkToAll(string.format("runway %d",_i)) end ]] - + -- Get units of group. local units=group:GetUnits() - + -- Loop over units. for _,_unit in pairs(units) do - + local unit=_unit --Wrapper.Unit#UNIT - + -- Check if unit is alive and not in air. if unit and unit:IsAlive() and not unit:InAir() then self:T(string.format("%s, checking if unit %s is on runway?",self:GetName(), unit:GetName())) - + -- Loop over runway spawn points. for _i,_coord in pairs(runwaypoints) do -- Distance between unit and spawn pos. local dist=unit:GetCoordinate():Get2DDistance(_coord) - + -- Mark unit spawn points for debugging. --unit:GetCoordinate():MarkToAll(string.format("unit %s distance to rwy %d = %d",unit:GetName(),_i, dist)) - + -- Check if unit is withing radius. if dist radius %.1f m. Despawn = %s.", self:GetName(), unit:GetName(), group:GetName(),_i, dist, radius, tostring(despawn))) --unit:FlareGreen() end - + end else - self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName())) - end + self:T(string.format("%s, checking if unit %s of group %s is on runway. Unit is NOT alive.",self:GetName(), unit:GetName(), group:GetName())) + end end else self:T(string.format("%s, checking if group %s is on runway. Group is NOT alive.",self:GetName(), group:GetName())) end - + return false end @@ -1079,15 +1079,15 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) return true end end - + -- Init no match. local match=false - - -- Standar case. + + -- Standar case. if Term_Type==termtype then match=true end - + -- Artificial cases. Combination of terminal types. if termtype==AIRBASE.TerminalType.OpenMedOrBig then if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig then @@ -1102,7 +1102,7 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype) match=true end end - + return match end @@ -1115,106 +1115,106 @@ function AIRBASE:GetRunwayData(magvar, mark) -- Runway table. local runways={} - + if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then return {} end -- Get spawn points on runway. local runwaycoords=self:GetParkingSpotsCoordinates(AIRBASE.TerminalType.Runway) - + -- Magnetic declination. magvar=magvar or UTILS.GetMagneticDeclination() - + local N=#runwaycoords local dN=2 local ex=false - + local name=self:GetName() - if name==AIRBASE.Nevada.Jean_Airport or - name==AIRBASE.Nevada.Creech_AFB or + if name==AIRBASE.Nevada.Jean_Airport or + name==AIRBASE.Nevada.Creech_AFB or name==AIRBASE.PersianGulf.Abu_Dhabi_International_Airport or name==AIRBASE.PersianGulf.Dubai_Intl or name==AIRBASE.PersianGulf.Shiraz_International_Airport or name==AIRBASE.PersianGulf.Kish_International_Airport then - + N=#runwaycoords/2 dN=1 ex=true end - - + + for i=1,N,dN do - + local j=i+1 if ex then --j=N+i j=#runwaycoords-i+1 end - + -- Coordinates of the two runway points. local c1=runwaycoords[i] --Core.Point#COORDINATES local c2=runwaycoords[j] --Core.Point#COORDINATES - + -- Heading of runway. local hdg=c1:HeadingTo(c2) - + -- Runway ID: heading=070° ==> idx="07" local idx=string.format("%02d", UTILS.Round((hdg-magvar)/10, 0)) - + -- Runway table. local runway={} --#AIRBASE.Runway runway.heading=hdg runway.idx=idx - runway.length=c1:Get2DDistance(c2) + runway.length=c1:Get2DDistance(c2) runway.position=c1 runway.endpoint=c2 - + -- Debug info. self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length)) - + -- Debug mark if mark then runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m", runway.idx, runway.heading, magvar, runway.length)) end - + -- Add runway. table.insert(runways, runway) - + end - + -- Get inverse runways local inverse={} for _,_runway in pairs(runways) do local r=_runway --#AIRBASE.Runway - - local runway={} --#AIRBASE.Runway + + local runway={} --#AIRBASE.Runway runway.heading=r.heading-180 if runway.heading<0 then runway.heading=runway.heading+360 - end + end runway.idx=string.format("%02d", math.max(0, UTILS.Round((runway.heading-magvar)/10, 0))) runway.length=r.length runway.position=r.endpoint runway.endpoint=r.position - + -- Debug info. self:T(string.format("Airbase %s: Adding runway id=%s, heading=%03d, length=%d m", self:GetName(), runway.idx, runway.heading, runway.length)) - + -- Debug mark if mark then runway.position:MarkToAll(string.format("Runway %s: true heading=%03d (magvar=%d), length=%d m", runway.idx, runway.heading, magvar, runway.length)) end - + -- Add runway. - table.insert(inverse, runway) + table.insert(inverse, runway) end - + -- Add inverse runway. for _,runway in pairs(inverse) do - table.insert(runways, runway) + table.insert(runways, runway) end - + return runways end @@ -1242,48 +1242,45 @@ function AIRBASE:GetActiveRunway(magvar) -- Get wind vector. local Vwind=self:GetCoordinate():GetWindWithTurbulenceVec3() local norm=UTILS.VecNorm(Vwind) - + -- Active runway number. local iact=1 - + -- Check if wind is blowing (norm>0). if norm>0 then - + -- Normalize wind (not necessary). Vwind.x=Vwind.x/norm Vwind.y=0 Vwind.z=Vwind.z/norm - + -- Loop over runways. local dotmin=nil for i,_runway in pairs(runways) do local runway=_runway --#AIRBASE.Runway - + -- Angle in rad. local alpha=math.rad(runway.heading) - + -- Runway vector. local Vrunway={x=math.cos(alpha), y=0, z=math.sin(alpha)} - + -- Dot product: parallel component of the two vectors. local dot=UTILS.VecDot(Vwind, Vrunway) - + -- Debug. --env.info(string.format("runway=%03d° dot=%.3f", runway.heading, dot)) - + -- New min? if dotmin==nil or dot