--- **Ops** - (R2.5) - Random Air Traffic. -- -- -- -- RAT2 creates random air traffic on the map. -- -- -- -- **Main Features:** -- -- * It's very random. -- -- === -- -- ### Author: **funkyfranky** -- @module Functional.Rat2 -- @image Functional_Rat2.png --- RAT2 class. -- @type RAT2 -- @field #string ClassName Name of the class. -- @field #boolean Debug Debug mode. Messages to all about status. -- @field #string lid Class id string for output to DCS log file. -- @field #table Qcraft Table of rat crafts. -- @extends Core.Fsm#FSM --- Be surprised! -- -- === -- -- ![Banner Image](..\Presentations\RAT2\RAT2_Main.png) -- -- # The RAT2 Concept -- -- -- -- @field #RAT2 RAT2 = { ClassName = "RAT2", Debug = false, lid = nil, Qcraft = {}, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a new RAT2 class object. -- @param #RAT2 self -- @return #RAT2 self. function RAT2:New() -- Inherit everthing from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #RAT2 -- Start State. self:SetStartState("Stopped") self.lid="RAT2 | " -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("Stopped", "Load", "Stopped") -- Load player scores from file. self:AddTransition("Stopped", "Start", "Running") -- Start RAT2 script. self:AddTransition("*", "Status", "*") -- Start RAT2 script. self:AddTransition("*", "Spawned", "*") -- A group was spawned. return self end --- Create a new RAT2 class object. -- @param #RAT2 self -- @param #RATAC ratcraft The aircraft to add. -- @return #RAT2 self. function RAT2:AddAircraft(ratcraft) -- Add ratcraft to spawn queue. table.insert(self.Qcraft, ratcraft) return self end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status Functions --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Start random air traffic. -- @param #RAT2 self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function RAT2:onafterStart(From, Event, To) self:__Status(-1) end --- Check spawn queue and spawn aircraft if necessary. -- @param #RAT2 self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function RAT2:onafterStatus(From, Event, To) self:I(string.format("RAT status %s", self:GetState())) -- Check queue of aircraft to spawn. self:_CheckQueueSpawn() self:__Status(-10) end --- Check spawn queue and spawn aircraft if necessary. -- @param #RAT2 self function RAT2:_CheckQueueSpawn() self:I(string.format("Checking flights")) -- Loop over all ratcraft. for i,_ratcraft in pairs(self.Qcraft) do local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT -- Get number of alive groups. local Nalive=ratcraft:_GetAliveGroups() for _,_flight in pairs(ratcraft.flights) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP self:I(string.format("Checking flight group %s in status %s", flight.groupname, flight:GetState())) if flight:IsInUtero() or flight:IsArrived() or flight:IsDead() then self:I(string.format("Try Spawning new flight group %s in status %s", flight.groupname, flight:GetState())) -- Get departure and parking. local departure, parking=ratcraft:GetDeparture() -- Get destination depending on departure. local destination=ratcraft:GetDestination(departure) if departure and destination and parking then self:I(string.format("Spawning new flight group %s in status %s", flight.groupname, flight:GetState())) -- Try to spawn a ratcraft group. local group=self:_SpawnRatcraft(ratcraft, flight, departure, destination, parking) -- Remove queue item and break loop. if group then -- We add a little delay self:__Spawned(0.1, group, flight, ratcraft) break end -- if group end -- if departure end -- if spawn flight end -- for flights end -- for ratcraft end --- Check spawn queue and spawn aircraft if necessary. -- @param #RAT2 self function RAT2:_CheckArrived() for _,_ratcraft in pairs(self.Qcraft) do local ratcraft=_ratcraft --Functional.RatCraft#RATCRAFT for _,_flight in pairs(ratcraft.flights) do local flight=_flight --Ops.FlightGroup#FLIGHTGROUP if flight:IsArrived() then -- Destroy group and create a remove unit event. flight.group:Destroy(nil) end end end end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM functions --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Function called after a RAT group was spawned. -- @param #RAT2 self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Wrapper.Group#GROUP group The spawned group. -- @param Ops.FlightGroup#FLIGHTGROUP flight The flight group. -- @param Functional.RatCraft#RATCRAFT ratcraft The ratcraft object. function RAT2:onafterSpawned(From, Event, To, group, flight, ratcraft) group:SmokeGreen() end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Spawn functions --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse. -- @param #RAT2 self -- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft to spawn. -- @param Ops.FlightGroup#FLIGHTGROUP flight Flight group. -- @param Functional.RatCraft#RATCRAFT.Departure departure Departure. -- @param Functional.RatCraft#RATCRAFT.Departure destination Destination. -- @param #table parking Parking data for this group. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. function RAT2:_SpawnRatcraft(ratcraft, flight, departure, destination, parking) -- Prepare the spawn template. local template=self:_SpawnAssetPrepareTemplate(ratcraft, flight.groupname) -- Get flight path if the group goes to another warehouse by itself. template.route.points=self:_GetFlightplan(ratcraft, AIRBASE:FindByName(departure.name), AIRBASE:FindByName(destination.name)) local airbase=AIRBASE:FindByName(departure.name) --self:_GetAirbase(departure) -- Get airbase ID and category. local AirbaseID = airbase:GetID() local AirbaseCategory = airbase:GetAirbaseCategory() -- Check enough parking spots. if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then --TODO Figure out what's necessary in this case. else if #parking<#template.units then local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) self:_DebugMessage(text) return nil end end -- Position the units. for i=1,#template.units do -- Unit template. local unit = template.units[i] if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then -- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense. local coord=airbase:GetCoordinate() unit.x=coord.x unit.y=coord.z unit.alt=coord.y unit.parking_id = nil unit.parking = nil else local coord=parking[i].Coordinate --Core.Point#COORDINATE local terminal=parking[i].TerminalID --#number if self.Debug then coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)) end unit.x=coord.x unit.y=coord.z unit.alt=coord.y unit.parking_id = nil unit.parking = terminal end if destination.parking then local spot=destination.parking[i] --Wrapper.Airbase#AIRBASE.ParkingSpot unit.parking_landing=spot.TerminalID end if ratcraft.livery==nil and #ratcraft.liveries>0 then ratcraft.livery=ratcraft.liveries[math.random(#ratcraft.liveries)] end -- Set livery. if ratcraft.livery then unit.livery_id = ratcraft.livery end -- Set skill. if ratcraft.skill then unit.skill= ratcraft.skill end end -- And template position. template.x = template.units[1].x template.y = template.units[1].y -- Uncontrolled spawning. template.uncontrolled=ratcraft.uncontrolled -- Debug info. self:T2({airtemplate=template}) -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP return group end --- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids. -- @param #RAT2 self -- @param Functional.RatCraft#RATCRAFT ratcraft Aircraft that will be spawned. -- @param #string groupname Name for the group to be spawned. -- @return #table Prepared new spawn template. function RAT2:_SpawnAssetPrepareTemplate(ratcraft, groupname) -- Create an own copy of the template! local template=UTILS.DeepCopy(ratcraft.template) -- Set unique name. template.name=groupname -- Set current(!) coalition and country. template.CoalitionID=ratcraft.coalition template.CountryID=ratcraft.country -- Nillify the group ID. template.groupId=nil -- No late activation. template.lateActivation=false -- Set and empty route. template.route = {} template.route.routeRelativeTOT=true template.route.points = {} -- Handle units. for i=1,#template.units do -- Unit template. local unit=template.units[i] -- Nillify the unit ID. unit.unitId=nil -- Set unit name: -01, -02, ... unit.name=string.format("%s-%02d", template.name , i) end return template end --@param #RCRAFT.Attribute _attribute Generlized attribute of unit. --- Get the proper terminal type based on generalized attribute of the group. --@param #RAT2 self --@param #number _category Airbase category. --@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group. function RAT2:_GetTerminal(_attribute, _category) -- Default terminal is "large". local _terminal=AIRBASE.TerminalType.OpenBig if _attribute==RCRAFT.Attribute.FIGHTER then -- Fighter ==> small. _terminal=AIRBASE.TerminalType.FighterAircraft elseif _attribute==RCRAFT.Attribute.BOMBER or _attribute==RCRAFT.Attribute.TRANSPORTPLANE or _attribute==RCRAFT.Attribute.TANKER or _attribute==RCRAFT.Attribute.AWACS then -- Bigger aircraft. _terminal=AIRBASE.TerminalType.OpenBig elseif _attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO then -- Helicopter. _terminal=AIRBASE.TerminalType.HelicopterUsable else --_terminal=AIRBASE.TerminalType.OpenMedOrBig end -- For ships, we allow medium spots for all fixed wing aircraft. There are smaller tankers and AWACS aircraft that can use a carrier. if _category==Airbase.Category.SHIP then if not (_attribute==RCRAFT.Attribute.TRANSPORTHELO or _attribute==RCRAFT.Attribute.ATTACKHELO) then _terminal=AIRBASE.TerminalType.OpenMedOrBig end end return _terminal end --- Seach unoccupied parking spots at the airbase for a list of assets. For each asset group a list of parking spots is returned. -- During the search also the not yet spawned asset aircraft are considered. -- If not enough spots for all asset units could be found, the routine returns nil! -- @param #RAT2 self -- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots. -- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. function RAT2:_FindParking(airbase, ratcraft) -- Init default local scanradius=50 local scanunits=true local scanstatics=true local scanscenery=false local verysafe=false -- Function calculating the overlap of two (square) objects. local function _overlap(l1,l2,dist) local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance! local safe = (dist > safedist) self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe))) return safe end -- Get parking spot data table. This contains all free and "non-free" spots. local parkingdata=airbase:GetParkingSpotsTable() -- List of obstacles. local obstacles={} -- Loop over all parking spots and get the currently present obstacles. -- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay! for _,parkingspot in pairs(parkingdata) do -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID -- Scan a radius of 100 meters around the spot. local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) -- Check all units. for _,_unit in pairs(_units) do local unit=_unit --Wrapper.Unit#UNIT local _coord=unit:GetCoordinate() local _size=self:_GetObjectSize(unit:GetDCSObject()) local _name=unit:GetName() table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"}) end -- Check all statics. for _,static in pairs(_statics) do local _vec3=static:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) local _name=static:getName() local _size=self:_GetObjectSize(static) table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"}) end -- Check all scenery. for _,scenery in pairs(_sceneries) do local _vec3=scenery:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) local _name=scenery:getTypeName() local _size=self:_GetObjectSize(scenery) table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"}) end -- TODO check clients. Clients cannot be spawned. So we can loop over them. end -- Parking data for all assets. local parking={} -- Get terminal type of this asset local terminaltype=self:_GetTerminal(ratcraft.attribute, airbase:GetAirbaseCategory()) local Nunits=#ratcraft.templategroup:GetUnits() -- Loop over all units - each one needs a spot. for i=1,Nunits do -- Loop over all parking spots. local gotit=false for _,_parkingspot in pairs(parkingdata) do local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot -- Check correct terminal type for asset. We don't want helos in shelters etc. if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID local _toac=parkingspot.TOAC --env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles)) local free=true local problem=nil -- Safe parking using TO_AC from DCS result. if self.safeparking and _toac then free=false self:T("Parking spot %d is occupied by other aircraft taking off or landing.", _termid) end -- Loop over all obstacles. for _,obstacle in pairs(obstacles) do -- Check if aircraft overlaps with any obstacle. local dist=_spot:Get2DDistance(obstacle.coord) local safe=_overlap(ratcraft.size, obstacle.size, dist) -- Spot is blocked. if not safe then --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", _asset.templatename, _asset.uid, _termid, dist)) free=false problem=obstacle problem.dist=dist break else --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", _asset.templatename, _asset.uid, _termid, dist)) end end -- Check if spot is free if free then -- Add parkingspot for this asset unit. table.insert(parking, parkingspot) self:T(self.lid..string.format("Parking spot #%d is free for ratcraft unit id=%d!", _termid, i)) -- Add the unit as obstacle so that this spot will not be available for the next unit. table.insert(obstacles, {coord=_spot, size=ratcraft.size, name=ratcraft.templatename, type="ratcraft"}) -- Break loop over parking spots. gotit=true break else -- Debug output for occupied spots. self:T(self.lid..string.format("Parking spot #%d is occupied or not big enough!", _termid)) if self.Debug then local coord=problem.coord --Core.Point#COORDINATE local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) coord:MarkToAll(string.format(text)) end end end -- check terminal type end -- loop over parking spots -- No parking spot for at least one unit :( if not gotit then self:T(self.lid..string.format("WARNING: No free parking spot for ratcraft unit i=%d", i)) return nil end end -- loop over units return parking end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Flightplan functions --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Calculate the maximum height an aircraft can reach for the given parameters. -- @param #RAT2 self -- @param #number D Total distance in meters from Departure to holding point at destination. -- @param #number alphaC Climb angle in rad. -- @param #number alphaD Descent angle in rad. -- @param #number Hdep AGL altitude of departure point. -- @param #number Hdest AGL altitude of destination point. -- @param #number Deltahhold Relative altitude of holding point above destination. -- @return #number Maximum height the aircraft can reach. function RAT2:_GetMaxHeight(D, alphaC, alphaD, Hdep, Hdest, Deltahhold) local Hhold=Hdest+Deltahhold local hdest=Hdest-Hdep local hhold=hdest+Deltahhold local Dp=math.sqrt(D^2 + hhold^2) local alphaS=math.atan(hdest/D) -- slope angle local alphaH=math.atan(hhold/D) -- angle to holding point (could be necative!) local alphaCp=alphaC-alphaH -- climb angle with slope local alphaDp=alphaD+alphaH -- descent angle with slope -- ASA triangle. local gammap=math.pi-alphaCp-alphaDp local sCp=Dp*math.sin(alphaDp)/math.sin(gammap) local sDp=Dp*math.sin(alphaCp)/math.sin(gammap) -- Max height from departure. local hmax=sCp*math.sin(alphaC) return hmax end --- Make a flight plan from a departure to a destination airport. -- @param #RAT2 self -- @param Functional.RatCraft#RATCRAFT ratcraft Ratcraft object. -- @param Wrapper.Airbase#AIRBASE departure Departure airbase. -- @param Wrapper.Airbase#AIRBASE destination Destination airbase. -- @return #table Table of flightplan waypoints. -- @return #table Table of flightplan coordinates. function RAT2:_GetFlightplan(ratcraft, departure, destination) -- Parameters local Vmax=ratcraft.speedmax local Range=ratcraft.range local category=ratcraft.category local ceiling=ratcraft.DCSdesc.Hmax local Vymax=ratcraft.DCSdesc.VyMax -- Max cruise speed 90% of max speed. local VxCruiseMax=0.90*Vmax -- Min cruise speed 70% of max cruise or 600 km/h whichever is lower. local VxCruiseMin = math.min(VxCruiseMax*0.75, 750) -- Cruise speed (randomized). Expectation value at midpoint between min and max. local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax) -- Climb speed 90% ov Vmax but max 720 km/h. local VxClimb = math.min(Vmax*0.90, 720) -- Descent speed 60% of Vmax but max 500 km/h. local VxDescent = math.min(Vmax*0.60, 500) -- Holding speed is 90% of descent speed. local VxHolding = VxDescent*0.9 -- Final leg is 90% of holding speed. local VxFinal = VxHolding*0.9 -- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate. local VyClimb=math.min(7.6, Vymax) -- Climb angle in rad. local AlphaClimb=math.rad(4) -- Descent angle in rad. Moderate 4 degrees. local AlphaDescent=math.rad(4) -- Expected cruise level (peak of Gaussian distribution) local FLcruise_expect=150*RAT.unit.FL2m if category==Group.Category.HELICOPTER then FLcruise_expect=1000 -- 1000 m ASL end ------------------------- --- DEPARTURE AIRPORT --- ------------------------- -- Coordinates of departure point. local Pdeparture=departure:GetCoordinate() -- Height ASL of departure point. local H_departure=Pdeparture.y --------------------------- --- DESTINATION AIRPORT --- --------------------------- -- Position of destination airport. local Pdestination=destination:GetCoordinate() -- Height ASL of destination airport/zone. local H_destination=Pdestination.y ----------------------------- --- DESCENT/HOLDING POINT --- ----------------------------- -- Get a random point between 5 and 10 km away from the destination. local Rhmin=5000 local Rhmax=10000 -- For helos we set a distance between 500 to 1000 m. if category==Group.Category.HELICOPTER then Rhmin=500 Rhmax=1000 end -- Coordinates of the holding point. y is the land height at that point. local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax, Rhmin) -- Distance from holding point to final destination (not used). local d_holding=Pholding:Get2DDistance(Pdestination) -- AGL height of holding point. local H_holding=Pholding.y --------------- --- GENERAL --- --------------- -- We go directly to the holding point not the destination airport. From there, planes are guided by DCS to final approach. local heading=Pdeparture:HeadingTo(Pholding) local d_total=Pdeparture:Get2DDistance(Pholding) ------------------------------ --- Holding Point Altitude --- ------------------------------ -- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL. local h_holding=1200 if category==Group.Category.HELICOPTER then h_holding=150 end h_holding=UTILS.Randomize(h_holding, 0.2) -- Max holding altitude. local DeltaholdingMax=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0) if h_holding>DeltaholdingMax then h_holding=math.abs(DeltaholdingMax) end -- This is the height ASL of the holding point we want to fly to. local Hh_holding=H_holding+h_holding --------------------------- --- Max Flight Altitude --- --------------------------- -- Get max flight altitude relative to H_departure. local h_max=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding) -- Max flight level ASL aircraft can reach for given angles and distance. local FLmax = h_max+H_departure --CRUISE -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. local FLmin=math.max(H_departure, Hh_holding) -- Ensure that FLmax not above its service ceiling. FLmax=math.min(FLmax, ceiling) -- If the route is very short we set FLmin a bit lower than FLmax. if FLmin>FLmax then FLmin=FLmax end -- Expected cruise altitude - peak of gaussian distribution. if FLcruise_expectFLmax then FLcruise_expect=FLmax end -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax) -- Climb and descent heights. local h_climb = FLcruise - H_departure local h_descent = FLcruise - Hh_holding -- Get distances. local d_climb = h_climb/math.tan(AlphaClimb) local d_descent = h_descent/math.tan(AlphaDescent) local d_cruise = d_total-d_climb-d_descent -- Debug. local text=string.format("Flight plan:\n") text=text..string.format("Vx max = %.2f km/h\n", Vmax) text=text..string.format("Vx climb = %.2f km/h\n", VxClimb*3.6) text=text..string.format("Vx cruise = %.2f km/h\n", VxCruise*3.6) text=text..string.format("Vx descent = %.2f km/h\n", VxDescent*3.6) text=text..string.format("Vx holding = %.2f km/h\n", VxHolding*3.6) text=text..string.format("Vx final = %.2f km/h\n", VxFinal*3.6) text=text..string.format("Vy max = %.2f m/s\n", Vymax) text=text..string.format("Vy climb = %.2f m/s\n", VyClimb) text=text..string.format("Alpha Climb = %.2f Deg\n", math.deg(AlphaClimb)) text=text..string.format("Alpha Descent = %.2f Deg\n", math.deg(AlphaDescent)) text=text..string.format("Dist climb = %.3f km\n", d_climb/1000) text=text..string.format("Dist cruise = %.3f km\n", d_cruise/1000) text=text..string.format("Dist descent = %.3f km\n", d_descent/1000) text=text..string.format("Dist total = %.3f km\n", d_total/1000) text=text..string.format("h_climb = %.3f km\n", h_climb/1000) text=text..string.format("h_desc = %.3f km\n", h_descent/1000) text=text..string.format("h_holding = %.3f km\n", h_holding/1000) text=text..string.format("h_max = %.3f km\n", h_max/1000) text=text..string.format("FL min = %.3f km\n", FLmin/1000) text=text..string.format("FL expect = %.3f km\n", FLcruise_expect/1000) text=text..string.format("FL cruise * = %.3f km\n", FLcruise/1000) text=text..string.format("FL max = %.3f km\n", FLmax/1000) text=text..string.format("Ceiling = %.3f km\n", ceiling/1000) text=text..string.format("Max range = %.3f km\n", Range/1000) self:T(self.lid..text) -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. if d_cruise<0 then d_cruise=100 end ------------------------ --- Create Waypoints --- ------------------------ -- Waypoints and coordinates local wp={} local c={} --- Departure/Take-off c[#c+1]=Pdeparture wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure") --- Begin of Cruise local Pcruise=Pdeparture:Translate(d_climb, heading) Pcruise.y=FLcruise c[#c+1]=Pcruise wp[#wp+1]=Pcruise:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Cruise") --- Descent local Pdescent=Pcruise:Translate(d_cruise, heading) Pdescent.y=FLcruise c[#c+1]=Pdescent wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent") --- Holding point Pholding.y=H_holding+h_holding c[#c+1]=Pholding wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding") --- Final destination. c[#c+1]=Pdestination wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination") -- Mark points at waypoints for debugging. if self.Debug or true then for i,coord in pairs(c) do local coord=coord --Core.Point#COORDINATE local dist=0 if i>1 then dist=coord:Get2DDistance(c[i-1]) end coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i, dist/1000)) end end return wp,c end