diff --git a/Moose Development/Moose/AI/AI_RAT.lua b/Moose Development/Moose/AI/AI_RAT.lua index d20bca4ee..cfe838309 100644 --- a/Moose Development/Moose/AI/AI_RAT.lua +++ b/Moose Development/Moose/AI/AI_RAT.lua @@ -1,22 +1,60 @@ +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- ** AI ** -- @module AI_RAT +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Some ID to identify where we are -- #string myid myid="RAT | " +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- RAT class -- @type RAT --- @field ClassName +-- @field #string ClassName +-- @field #boolean debug -- @field #string prefix --- @field #RAT +-- @field #number sapwndelay +-- @field #number spawninterval +-- @field #number coalition +-- @field #string category +-- @field #string friendly +-- @field #table ctable +-- @field #table aircraft +-- @field #number Vcruisemax +-- @field #number Vclimb +-- @field #number AlphaDescent +-- @field #string roe +-- @field #string takeoff +-- @field #number mindist +-- @field #number maxdist +-- @field #table airports_map +-- @field #table airports +-- @field #table airports_departure +-- @field #table airports_destination +-- @field #boolean random_departure +-- @field #boolean random_destination +-- @field #table departure_zones +-- @field #table departure_ports +-- @field #table destination_ports +-- @field #number Rzone +-- @field #table ratcraft +-- @field #number markerid +-- @field #number marker0 +-- @field #table RAT -- @extends #SPAWN + + +--- RAT class +-- @field #RAT RAT RAT={ ClassName = "RAT", -- Name of class: RAT = Random Air Traffic. - debug=true, -- Turn debug messages on or off. + debug=false, -- Turn debug messages on or off. prefix=nil, -- Prefix of the template group defined in the mission editor. - spawndelay=5, -- Delay time in seconds before first spawning happens. - spawninterval=2, -- Interval between spawning units/groups. Note that we add a randomization of 10%. + spawndelay=1, -- Delay time in seconds before first spawning happens. + spawninterval=1, -- Interval between spawning units/groups. Note that we add a randomization of 10%. coalition = nil, -- Coalition of spawn group template. category = nil, -- Category of aircarft: "plane" or "heli". friendly = "same", -- Possible departure/destination airport: all=blue+red+neutral, same=spawn+neutral, spawnonly=spawn, blue=blue+neutral, blueonly=blue, red=red+neutral, redonly=red. @@ -33,24 +71,42 @@ RAT={ airports={}, -- All airports of friedly coalitions. airports_departure={}, -- Possible departure airports if unit/group is spawned at airport, spawnpoint=air or spawnpoint=airport. airports_destination={}, -- Possible destination airports if unit does not fly "overseas", destpoint=overseas or destpoint=airport. - departure_name="random", -- Name of the departure airport. Default is "random" for a randomly chosen one of the coalition airports. - destination_name="random",-- Name of the destination airport. Default is "random" for a randomly chosen one of the coalition airports. random_departure=true, -- By default a random friendly airport is chosen as departure. random_destination=true, -- By default a random friendly airport is chosen as destination. departure_zones={}, -- Array containing the names of the departure zones. - departure_ports={}, -- Array containing the names of the destination zones. - zones_departure={}, -- Departure zones for air start. + departure_ports={}, -- Array containing the names of the departure zones. + destination_ports={}, -- Array containing the names of the destination zones. Rzone=5000, -- Radius of departure zones in meters. ratcraft={}, -- Array with the spawned RAT aircraft. - markerid=0, + markerid=0, -- Running number of the ID of markers on the F10 map. + marker0=nil, -- Specific marker ID offset. } +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- RAT categories. -- @field #RAT cat RAT.cat={ plane="plane", heli="heli" } +--- RAT unit conversions. +-- @field #RAT unit +RAT.unit={ + meter2feet=1, + nm2km=1, + FL2m=1, + --[[ + -- unit conversions + local ft2meter=0.305 + local kmh2ms=0.278 + local FL2m=30.48 + local nm2km=1.852 + local nm2m=1852 + ]] +} + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --TODO list: --DONE: Add scheduled spawn. @@ -70,13 +126,15 @@ RAT.cat={ --DONE: Check that FARPS are not used as airbases for planes. Don't know if they appear in list of airports. --DONE: Add cases for helicopters. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Creates a new RAT object. -- @param #RAT self -- @param #string prefix Prefix of the (template) group name defined in the mission editor. -- @param #string friendly Friendly coalitions from which airports can be used. -- "all"=neutral+red+blue, "same"=spawn coalition+neutral, "sameonly"=spawn coalition, "blue"=blue+neutral, "blueonly"=blue, "red"=red+neutral, "redonly"=red, "neutral"=neutral. -- Default is "same", so aircraft will use airports of the coalition their spawn template has plus all neutral airports. --- @return #RAT self Object of RAT class. +-- @return #RAT Object of RAT class. -- @return #nil Nil if the group does not exists in the mission editor. function RAT:New(prefix, friendly) @@ -113,10 +171,155 @@ function RAT:New(prefix, friendly) -- Get all airports of this map beloning to friendly coalition(s). self:_GetAirportsOfCoalition() + + self.marker0=math.random(1000000) return self end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous. +-- Default is "takeoff-hot" for a start at airport with engines already running. +-- @param #RAT self +-- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random". +-- @usage RAT:Takeoff("hot") will spawn RAT objects at airports with engines started. +-- @usage RAT:Takeoff("cold") will spawn RAT objects at airports with engines off. +-- @usage RAT:Takeoff("air") will spawn RAT objects in air over random airports or within pre-defined zones. +function RAT:SetTakeoff(type) + + -- All possible types for random selection. + local types={"takeoff-cold", "takeoff-hot", "air"} + + local _Type + if type:lower()=="takeoff-cold" or type:lower()=="cold" then + _Type="takeoff-cold" + elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then + _Type="takeoff-hot" + elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then + _Type="takeoff-runway" + elseif type:lower()=="air" then + _Type="air" + elseif type:lower()=="random" then + _Type=types[math.random(#types)] + else + _Type="takeoff-hot" + end + + self.takeoff=_Type +end + + +--- Set possible departure ports. This can be an airport or a zone defined in the mission editor. +-- @param #RAT self +-- @param #string names Name or table of names of departure airports or zones. +-- @usage RAT:SetDeparture("Sochi-Adler") will spawn RAT objects at Sochi-Adler airport. +-- @usage RAT:SetDeparture({"Sochi-Adler", "Gudauta"}) will spawn RAT aircraft radomly at Sochi-Adler or Gudauta airport. +-- @usage RAT:SetDeparture({"Zone A", "Gudauta"}) will spawn RAT aircraft in air randomly within Zone A, which has to be defined in the mission editor, or within a zone around Gudauta airport. Note that this also requires RAT:takeoff("air") to be set. +function RAT:SetDeparture(names) + self:E({"SetDeparture Names", names}) + + -- Random departure is deactivated now that user specified departure ports. + self.random_departure=false + + if type(names)=="table" then + + -- we did get a table of names + for _,name in pairs(names) do + + if self:_AirportExists(name) then + -- If an airport with this name exists, we put it in the ports array. + table.insert(self.departure_ports, name) + else + -- If it is not an airport, we assume it is a zone. + table.insert(self.departure_zones, name) + end + + end + + elseif type(names)=="string" then + + if self:_AirportExists(names) then + -- If an airport with this name exists, we put it in the ports array. + table.insert(self.departure_ports, names) + else + -- If it is not an airport, we assume it is a zone. + table.insert(self.departure_zones, names) + end + + else + -- error message + error("Input parameter must be a string or a table!") + end + +end + +--- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly. +-- @param #RAT self +-- @param #string names Name of the destination airport or table of destination airports. +function RAT:SetDestination(names) + + -- Random departure is deactivated now that user specified departure ports. + self.random_destination=false + + if type(names)=="table" then + + for _,name in pairs(names) do + table.insert(self.destination_ports, name) + end + + elseif type(names)=="string" then + + self.destination_ports={names} + + else + -- Error message. + error("Input parameter must be a string or a table!") + end + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Spawn the AI aircraft. +-- @param #RAT self +-- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft. +-- @param #string name (Optional) Name of the spawn group (for debugging only). +function RAT:Spawn(naircraft, name) + + -- Number of aircraft to spawn. Default is one. + naircraft=naircraft or 1 + + -- some of group for debugging + --TODO: remove name from input parameter and make better unique RAT AI name + name=name or "RAT AI "..self.aircraft.type + + -- debug message + local text="Spawning "..naircraft.." aircraft of group "..self.prefix.." with name "..name.." of type "..self.aircraft.type..".\n" + text=text.."Takeoff type: "..self.takeoff.."\n" + text=text.."Friendly airports: "..self.friendly + env.info(myid..text) + if self.debug then + MESSAGE:New(text, 60, "Info"):ToAll() + end + + -- Schedule spawning of aircraft. + local Tstart=self.spawndelay + local dt=self.spawninterval + if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then + -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed. + dt=math.max(dt, 180) + end + local Tstop=Tstart+dt*(naircraft-1) + SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop) + + -- Status report scheduler. + SCHEDULER:New(nil, self.Status, {self}, 30, 30) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Initialize basic parameters of the aircraft based on its (template) group in the mission editor. -- @param #RAT self -- @param Dcs.DCSWrapper.Group#Group DCSgroup Group of the aircraft in the mission editor. @@ -149,10 +352,6 @@ function RAT:_InitAircraft(DCSgroup) error(myid.."Group of RAT is neither airplane nor helicopter!") end - -- Define a first departure zone around the point where the group template in the ME was placed. - local ZoneTemplate = ZONE_GROUP:New( "Template", GROUP:FindByName(self.prefix), self.Rzone) - table.insert(self.zones_departure, ZoneTemplate) - -- Get type of aircraft. self.aircraft.type=DCStype @@ -204,7 +403,7 @@ function RAT:_InitAircraft(DCSgroup) -- send debug message local text=string.format("Aircraft parameters:\n") - text=text..string.format("Category = %s\n", self.category) + text=text..string.format("Category = %s\n", self.category) text=text..string.format("Max speed = %6.1f m/s.\n", self.aircraft.Vmax) text=text..string.format("Max cruise speed = %6.1f m/s.\n", self.aircraft.Vcruise) text=text..string.format("Max climb speed = %6.1f m/s.\n", self.aircraft.Vymax) @@ -223,45 +422,7 @@ function RAT:_InitAircraft(DCSgroup) end - ---- Spawn the AI aircraft. --- @param #RAT self --- @param #number naircraft (Optional) Number of aircraft to spawn. Default is one aircraft. --- @param #string name (Optional) Name of the spawn group (for debugging only). -function RAT:Spawn(naircraft, name) - - -- Number of aircraft to spawn. Default is one. - naircraft=naircraft or 1 - - -- some of group for debugging - --TODO: remove name from input parameter and make better unique RAT AI name - name=name or "RAT AI "..self.aircraft.type - - -- debug message - local text="Spawning "..naircraft.." aircraft of group "..self.prefix.." with name "..name.." of type "..self.aircraft.type..".\n" - text=text.."Takeoff type: "..self.takeoff.."\n" - text=text.."Friendly airports: "..self.friendly - env.info(myid..text) - if self.debug then - MESSAGE:New(text, 60, "Info"):ToAll() - end - - -- Schedule spawning of aircraft. - --TODO: make self.SpawnInterval and sef.spawndelay user input - local Tstart=self.spawndelay - local dt=self.spawninterval - if self.takeoff:lower()=="takeoff-runway" or self.takeoff:lower()=="runway" then - -- Ensure that interval is >= 180 seconds if spawn at runway is chosen. Aircraft need time to takeoff or the runway gets jammed. - dt=math.max(dt, 180) - end - local Tstop=Tstart+dt*(naircraft-1) - SCHEDULER:New(nil, self._SpawnWithRoute, {self}, Tstart, dt, 0.1, Tstop) - - -- Status report scheduler. - SCHEDULER:New(nil, self.Status, {self}, 30, 30) - -end - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Spawn the AI aircraft with a route. -- Sets the departure and destination airports and waypoints. @@ -305,6 +466,431 @@ function RAT:_SpawnWithRoute() --self:HandleEvent(EVENTS.Crash, self._OnCrash) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set the route of the AI plane. Due to DCS landing bug, this has to be done before the unit is spawned. +-- @param #RAT self +-- @return Wrapper.Airport#AIRBASE Departure airbase. +-- @return Wrapper.Airport#AIRBASE Destination airbase. +-- @return #table Table of flight plan waypoints. +function RAT:_SetRoute() + + -- unit conversions + local ft2meter=0.305 + local kmh2ms=0.278 + local FL2m=30.48 + local nm2km=1.852 + + -- DEPARTURE AIRPORT + -- Departure airport or zone. + local departure=self:_SetDeparture() + + -- Coordinates of departure point. + local Pdeparture + if self.takeoff=="air" then + -- For an air start, we take a random point within the spawn zone. + local vec2=departure:GetRandomVec2() + --Pdeparture=COORDINATE:New(vec2.x, self.aircraft.FLcruise, vec2.y) + Pdeparture=COORDINATE:NewFromVec2(vec2) + else + Pdeparture=departure:GetCoordinate() + end + + -- Height ASL of departure point. + local H_departure + if self.takeoff=="air" then + -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos). + local Hmin + if self.category=="plane" then + Hmin=1000 + else + Hmin=50 + end + H_departure=self:_Randomize(self.aircraft.FLcruise*0.7, 0.3, Pdeparture.y+Hmin, self.aircraft.FLcruise) + else + H_departure=Pdeparture.y + end + + -- DESTINATION AIRPORT + -- Get all destination airports within reach and at least 10 km away from departure. + self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff) + + -- Pick a destination airport. + local destination=self:_SetDestination() + + -- Check that departure and destination are not the same. Should not happen due to mindist. + if destination:GetName()==departure:GetName() then + local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID() + MESSAGE:New(text, 120):ToAll() + error(myid..text) + end + + -- Coordinates of destination airport. + local Pdestination=destination:GetCoordinate() + -- Height ASL of destination airport. + local H_destination=Pdestination.y + + -- DESCENT/HOLDING POINT + -- Get a random point between 10 and 20 km away from the destination. + local Vholding + if self.category=="plane" then + Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000) + else + -- For helos we set a distance between 500 to 1000 m. + Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500) + end + -- Coordinates of the holding point. y is the land height at that point. + local Pholding=COORDINATE:NewFromVec2(Vholding) + + -- Holding point altitude. For planes between 800 and 1200 m AGL. For helos 80 to 120 m AGL. + local h_holding + if self.category=="plane" then + h_holding=1000 + else + h_holding=100 + end + h_holding=self:_Randomize(h_holding, 0.2) + + -- Distance from holding point to destination. + local d_holding=Pholding:Get2DDistance(Pdestination) + + -- GENERAL + -- heading from departure to holding point of destination + local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination + + -- total distance between departure and holding point (+last bit to destination) + local d_total=Pdeparture:Get2DDistance(Pholding) + + -- CLIMB and DESCENT angles + -- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first. + local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1) + local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1) + + --CRUISE + -- Set min/max cruise altitudes. + local FLmax + local FLmin + local FLcruise=self.aircraft.FLcruise + if self.category=="plane" then + -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. + FLmin=math.max(H_departure, H_destination+h_holding) + -- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates. + if self.takeoff=="air" then + -- This is the case where we only descent to the ground at the given descent angle. + -- TODO: should this not better be h_holding Pholding.y? + FLmax=d_total*math.tan(AlphaDescent)+H_destination + else + FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure) + end + -- If the route is very short we set FLmin a bit lower than FLmax. + if FLmin>FLmax then + FLmin=FLmax*0.8 + end + -- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach. + if FLcruise>FLmax then + FLcruise=FLmax*0.9 + end + else + -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. + FLmin=math.max(H_departure, H_destination)+50 + FLmax=math.max(H_departure, H_destination)+1000 + end + + -- Ensure that FLmax not above 90% of service ceiling. + FLmax=math.min(FLmax, self.aircraft.ceiling*0.9) + + -- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax. + FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax) + + + if self.takeoff=="air" then + H_departure=math.min(H_departure,FLmax) + end + + -- CLIMB + -- Height of climb relative to ASL height of departure airport. + local h_climb=FLcruise-H_departure + -- x-distance of climb part + local d_climb=math.abs(h_climb/math.tan(AlphaClimb)) + -- time of climb in seconds + local t_climb=h_climb/self.aircraft.Vclimb + + -- DESCENT + -- Height difference for descent form cruise alt to holding point. + local h_descent=FLcruise-h_holding-Pholding.y + -- x-distance of descent part + local d_descent=math.abs(h_descent/math.tan(AlphaDescent)) + + -- CRUISE + -- Distance of the cruising part. This should in principle not become negative, but can happen for very short legs. + local d_cruise=d_total-d_climb-d_descent + + -- debug message + local text=string.format("Route distances:\n") + text=text..string.format("d_climb = %6.1f km\n", d_climb/1000) + text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000) + text=text..string.format("d_descent = %6.1f km\n", d_descent/1000) + text=text..string.format("d_holding = %6.1f km\n", d_holding/1000) + text=text..string.format("d_total = %6.1f km\n", d_total/1000) + text=text..string.format("Route heights:\n") + text=text..string.format("H_departure = %6.1f m ASL\n", H_departure) + text=text..string.format("H_destination = %6.1f m ASL\n", H_destination) + text=text..string.format("h_climb = %6.1f m AGL\n", h_climb) + text=text..string.format("h_descent = %6.1f m\n", h_descent) + text=text..string.format("h_holding = %6.1f m AGL\n", h_holding) + text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y) + text=text..string.format("Alpha_climb = %6.1f Deg\n", math.deg(AlphaClimb)) + text=text..string.format("Alpha_descent = %6.1f Deg\n", math.deg(AlphaDescent)) + text=text..string.format("FLmin = %6.1f m ASL\n", FLmin) + text=text..string.format("FLmax = %6.1f m ASL\n", FLmax) + text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise) + text=text..string.format("Heading = %6.1f Degrees", heading) + env.info(myid..text) + if self.debug then + MESSAGE:New(text, 60):ToAll() + end + + -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). + local c0=Pdeparture + local c1=c0:Translate(d_climb, heading) + local c2=c1:Translate(d_cruise, heading) + local c3=c2:Translate(d_descent, heading) + local c3=Pholding + local c4=Pdestination + + --Convert coordinates into route waypoints. + local wp0=self:_Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure) + local wp1=self:_Waypoint("climb", c1, self.aircraft.Vmax, FLcruise) + local wp2=self:_Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise) + --TODO: add the possibility for a holing point, i.e. we circle a bit before final approach. + --local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding) + local wp3=self:_Waypoint("holding", c3, self.aircraft.Vmin, h_holding) + local wp4=self:_Waypoint("landing", c4, self.aircraft.Vmin, 2, destination) + + -- set waypoints + local waypoints = {wp0, wp1, wp2, wp3, wp4} + + self:_SetMarker("Takeoff and begin of climb.", c0) + self:_SetMarker("End of climb and begin of cruise", c1) + self:_SetMarker("End of Cruise and begin of descent", c2) + self:_SetMarker("Holding Point", c3) + self:_SetMarker("Final Destination", c4) + + -- some info on the route as message + self:_Routeinfo(waypoints, "Waypoint info in set_route:") + + -- return departure, destination and waypoints + return departure, destination, waypoints + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly. +-- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input. +-- @param #RAT self +-- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport. +function RAT:_SetDeparture() + + -- Array of possible departure airports or zones. + local departures={} + + if self.takeoff=="air" then + + if self.random_departure then + + -- Air start above a random airport. + for _,airport in pairs(self.airports)do + table.insert(departures, airport:GetZone()) + end + + else + + -- Put all specified zones in table. + for _,name in pairs(self.departure_zones) do + self:E(self.departure_zones) + env.info(myid.."Zone name: "..name) + table.insert(departures, ZONE:New(name)) + end + -- Put all specified airport zones in table. + for _,name in pairs(self.departure_ports) do + table.insert(departures, AIRBASE:FindByName(name):GetZone()) + end + + end + + else + + if self.random_departure then + + -- All friendly departure airports. + for _,airport in pairs(self.airports) do + table.insert(departures, airport) + end + + else + + -- All airports specified by user + for _,name in pairs(self.departure_ports) do + table.insert(departures, AIRBASE:FindByName(name)) + end + + end + end + + -- Select departure airport or zone. + local departure=departures[math.random(#departures)] + + local text + if self.takeoff=="air" then + text="Chosen departure zone: "..departure:GetName() + else + text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")" + end + env.info(myid..text) + MESSAGE:New(text, 60):ToAll() + + return departure +end + + +--- Set the destination airport of the AI. If no airport name is given an airport from the coalition is chosen randomly. +-- @param #RAT self +-- @return Wrapper.Airbase#AIRBASE Destination airport. +function RAT:_SetDestination() + + -- Array of possible destination airports. + local destinations={} + + if self.random_destination then + + -- All airports of friendly coalitons. + for _,airport in pairs(self.airports_destination) do + table.insert(destinations, airport) + end + + else + + -- All airports specified by user. + for _,name in pairs(self.destination_ports) do + table.insert(destinations, AIRBASE:FindByName(name)) + end + + end + + -- Randomly select one possible destination. + local destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE + + local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" + self:E(destination:GetDesc()) + env.info(myid..text) + MESSAGE:New(text, 60):ToAll() + + return destination +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get all possible destination airports depending on departure position. +-- The list is sorted w.r.t. distance to departure position. +-- @param #RAT self +-- @param Core.Point#COORDINATE q Coordinate of the departure point. +-- @param #number minrange Minimum range to q in meters. +-- @param #number maxrange Maximum range to q in meters. +function RAT:_GetDestinations(q, minrange, maxrange) + + local absolutemin=5000 -- Absolute minimum is 5 km. + minrange=minrange or absolutemin -- Default min is absolute min. + maxrange=maxrange or 10000000 -- Default max 10,000 km. + + -- Ensure that minrange is always > 10 km to ensure the destination != departure. + minrange=math.max(absolutemin, minrange) + + -- loop over all friendly airports + for _,airport in pairs(self.airports) do + local p=airport:GetCoordinate() + local distance=q:Get2DDistance(p) + -- check if distance form departure to destination is within min/max range + if distance>=minrange and distance<=maxrange then + table.insert(self.airports_destination, airport) + end + end + env.info(myid.."Number of possible destination airports = "..#self.airports_destination) + + if #self.airports_destination > 1 then + --- Compare distance of destination airports. + -- @param Core.Point#COORDINATE a Coordinate of point a. + -- @param Core.Point#COORDINATE b Coordinate of point b. + -- @return #list Table sorted by distance. + local function compare(a,b) + local qa=q:Get2DDistance(a:GetCoordinate()) + local qb=q:Get2DDistance(b:GetCoordinate()) + return qa < qb + end + table.sort(self.airports_destination, compare) + end + +end + + +--- Get all airports of the current map. +-- @param #RAT self +function RAT:_GetAirportsOfMap() + local _coalition + + for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE + + -- set coalition + if i==0 then + _coalition=coalition.side.NEUTRAL + elseif i==1 then + _coalition=coalition.side.RED + elseif i==2 then + _coalition=coalition.side.BLUE + end + + -- get airbases of coalition + local ab=coalition.getAirbases(i) + + -- loop over airbases and put them in a table + for _,airbase in pairs(ab) do -- loop over airbases + local _id=airbase:getID() + local _p=airbase:getPosition().p + local _name=airbase:getName() + local _myab=AIRBASE:FindByName(_name) + local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() + env.info(myid..text) + table.insert(self.airports_map, _myab) + end + + end +end + + +--- Get all "friendly" airports of the current map. +-- @param #RAT self +function RAT:_GetAirportsOfCoalition() + for _,coalition in pairs(self.ctable) do + for _,airport in pairs(self.airports_map) do + if airport:GetCoalition()==coalition then + airport:GetTypeName() + -- Remember that planes cannot land on FARPs. + -- TODO: Probably have to add ships as well! + if not (self.category=="plane" and airport:GetTypeName()=="FARP") then + table.insert(self.airports, airport) + end + end + end + end + + if #self.airports==0 then + local text="No possible departure/destination airports found!" + MESSAGE:New(text, 180):ToAll() + error(myid..text) + end +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Report status of RAT groups. -- @param #RAT self @@ -351,6 +937,8 @@ function RAT:_SetStatus(group, status) self.ratcraft[index].status=status end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Function is executed when a unit is spawned. -- @param #RAT self function RAT:_OnBirthDay(EventData) @@ -467,316 +1055,7 @@ function RAT:_OnCrash(EventData) end end - ---- Set name of departure airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly. --- @param #RAT self --- @param #string name Name of the departure airport or "random" for a randomly chosen one of the coalition. -function RAT:SetDeparture(name) - if name and AIRBASE:FindByName(name) then - self.departure_name=name - else - self.departure_name="random" - end -end - ---- Set departure zones for spawning the AI aircraft. --- @param #RAT self --- @param #table zonenames Table of zone names where spawning should happen. -function RAT:SetDepartureZones(zonenames) - self.zones_departure={} - local z - for _,name in pairs(zonenames) do - if name:lower()=="zone template" then - -- Zone with radius 5 km around the template group in the ME. - z=ZONE_GROUP:New("Zone Template", GROUP:FindByName(self.prefix), self.Rzone) - else - -- Zone defined my user in the ME. - z=ZONE:New(name) - end - if z then - table.insert(self.zones_departure, z) - else - error(myid.."A zone with name "..name.." does not exist!") - end - end -end - ---- Test if an airport exists on the current map. --- @param #RAT self --- @param #string name --- @return #boolean True if airport exsits, false otherwise. -function RAT:_AirportExists(name) - for _,airport in pairs(self.airports_map) do - if airport:GeName()==name then - return true - end - end - return false -end - - ---- Set possible departure ports. This can be an airport or a zone defined in the mission editor. --- @param #RAT self -function RAT:SetDepartureAll(names) - - -- Random departure is deactivated now that user specified departure ports. - self.random_departure=false - - if type(names)=="table" then - - -- we did get a table of names - for _,name in pairs(names) do - - if self:_AirportExists(name) then - -- If an airport with this name exists, we put it in the ports array. - table.insert(self.departure_ports,"name") - else - -- If it is not an airport, we assume it is a zone. - table.insert(self.departure_zones,"name") - end - - end - - elseif type(names)=="string" then - - if self:_AirportExists("names") then - -- If an airport with this name exists, we put it in the ports array. - table.insert(self.departure_ports, "names") - else - -- If it is not an airport, we assume it is a zone. - table.insert(self.departure_zones, "names") - end - - else - -- error message - error("Input parameter must be a string or a table!") - end - -end - ---- Set name of destination airport for the AI aircraft. If no name is given an airport from the coalition is chosen randomly. --- @param #RAT self --- @param #string name Name of the destination airport or "random" for a randomly chosen one of the coalition. -function RAT:SetDestination(name) - if name and AIRBASE:FindByName(name) then - self.destination_name=name - else - self.destination_name="random" - end -end - - ---- Set the departure airport of the AI. If no airport name is given explicitly an airport from the coalition is chosen randomly. --- If takeoff style is set to "air", we use zones around the airports or the zones specified by user input. --- @param #RAT self --- @return Wrapper.Airbase#AIRBASE Departure airport if spawning at airport. -function RAT:_SetDeparture() - - local departure - - -- Array containing possible departure airports or zones. - local departures={} - - if self.takeoff=="air" then - - if self.random_departure then - - -- Air start above a random airport. - for _,airport in pairs(self.airports)do - table.insert(departures, airport:GetZone()) - end - - else - - -- Put all specified zones in table. - for _,name in pairs(self.departure_zones) do - table.insert(departures, ZONE:New(name)) - end - -- Put all specified airport zones in table. - for _,name in pairs(self.departure_zones) do - table.insert(departures, AIRBASE:FindByName("name"):GetZone()) - end - - end ---[[ - if self.departure_name=="random" then - departure=self.zones_departure[math.random(#self.zones_departure)] - else - departure=ZONE:FindByName(self.departure_name) - end - - text="Chosen departure zone: "..departure:GetName() -]] - else - - if self.random_departure then - - -- All friendly departure airports. - departures=self.airports - - else - - for _,name in pairs(self.departure_ports) do - table.insert(departures, AIRBASE:FindByName(name)) - end - - end - end - ---[[ - if self.departure_name=="random" then - -- Get a random departure airport from all friendly coalition airports. - departure=self.airports[math.random(#self.airports)] - elseif AIRBASE:FindByName(self.departure_name) then - -- Take the explicit airport provided. - departure=AIRBASE:FindByName(self.departure_name) - else - -- If nothing else works, we randomly choose from friendly coalition airports. - departure=self.airports[math.random(#self.airports)] - end - - text="Chosen departure airport: "..departure:GetName().." with ID "..departure:GetID() - end -]] - - -- Select departure airport or zone. - local departure=departures[math.random(#departures)] - - local text - if self.takeoff=="air" then - text="Chosen departure zone: "..departure:GetName() - else - text="Chosen departure airport: "..departure:GetName().." (ID "..departure:GetID()..")" - end - env.info(myid..text) - MESSAGE:New(text, 60):ToAll() - - return departure -end - - ---- Set the destination airport of the AI. If no airport name is given an airport from the coalition is chosen randomly. --- @param #RAT self --- @return Wrapper.Airbase#AIRBASE Destination airport. -function RAT:_SetDestination() - local destination -- Wrapper.Airbase#AIRBASE - if self.destination_name=="random" then - -- Get random destination from all friendly airports within range. - destination=self.airports_destination[math.random(1, #self.airports_destination)] - elseif self.destination_name and AIRBASE:FindByName(self.destination_name) then - -- Take the explicit airport provided. - destination=AIRBASE:FindByName(self.destination_name) - else - -- If nothing else works, we randomly choose from frindly coalition airports. - destination=self.airports_destination[math.random(1, #self.airports_destination)] - end - local text="Chosen destination airport: "..destination:GetName().." with ID "..destination:GetID() - self:E(destination:GetDesc()) - env.info(myid..text) - MESSAGE:New(text, 60):ToAll() - return destination -end - - ---- Get all possible destination airports depending on departure position. --- The list is sorted w.r.t. distance to departure position. --- @param #RAT self --- @param Core.Point#COORDINATE q Coordinate of the departure point. --- @param #number minrange Minimum range to q in meters. --- @param #number maxrange Maximum range to q in meters. -function RAT:_GetDestinations(q, minrange, maxrange) - - local absolutemin=5000 -- Absolute minimum is 5 km. - minrange=minrange or absolutemin -- Default min is absolute min. - maxrange=maxrange or 10000000 -- Default max 10,000 km. - - -- Ensure that minrange is always > 10 km to ensure the destination != departure. - minrange=math.max(absolutemin, minrange) - - -- loop over all friendly airports - for _,airport in pairs(self.airports) do - local p=airport:GetCoordinate() - local distance=q:Get2DDistance(p) - -- check if distance form departure to destination is within min/max range - if distance>=minrange and distance<=maxrange then - table.insert(self.airports_destination, airport) - end - end - env.info(myid.."Number of possible destination airports = "..#self.airports_destination) - - if #self.airports_destination > 1 then - --- Compare distance of destination airports. - -- @param Core.Point#COORDINATE a Coordinate of point a. - -- @param Core.Point#COORDINATE b Coordinate of point b. - -- @return #list Table sorted by distance. - local function compare(a,b) - local qa=q:Get2DDistance(a:GetCoordinate()) - local qb=q:Get2DDistance(b:GetCoordinate()) - return qa < qb - end - table.sort(self.airports_destination, compare) - end - -end - - ---- Get all airports of the current map. --- @param #RAT self -function RAT:_GetAirportsOfMap() - local _coalition - - for i=0,2 do -- cycle coalition.side 0=NEUTRAL, 1=RED, 2=BLUE - - -- set coalition - if i==0 then - _coalition=coalition.side.NEUTRAL - elseif i==1 then - _coalition=coalition.side.RED - elseif i==2 then - _coalition=coalition.side.BLUE - end - - -- get airbases of coalition - local ab=coalition.getAirbases(i) - - -- loop over airbases and put them in a table - for _,airbase in pairs(ab) do -- loop over airbases - local _id=airbase:getID() - local _p=airbase:getPosition().p - local _name=airbase:getName() - local _myab=AIRBASE:FindByName(_name) - local text="Airport ID = ".._myab:GetID().." and Name = ".._myab:GetName()..", Category = ".._myab:GetCategory()..", TypeName = ".._myab:GetTypeName() - env.info(myid..text) - table.insert(self.airports_map, _myab) - end - - end -end - - ---- Get all "friendly" airports of the current map. --- @param #RAT self -function RAT:_GetAirportsOfCoalition() - for _,coalition in pairs(self.ctable) do - for _,airport in pairs(self.airports_map) do - if airport:GetCoalition()==coalition then - airport:GetTypeName() - -- Remember that planes cannot land on FARPs. - -- TODO: Probably have to add ships as well! - if not (self.category=="plane" and airport:GetTypeName()=="FARP") then - table.insert(self.airports, airport) - end - end - end - end - - if #self.airports==0 then - local text="No possible departure/destination airports found!" - MESSAGE:New(text, 180):ToAll() - error(myid..text) - end -end - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Create a waypoint that can be used with the Route command. -- @param #RAT self @@ -786,7 +1065,7 @@ end -- @param #number Altitude Altitude in m. -- @param Wrapper.Airbase#AIRBASE Airport Airport of object to spawn. -- @return #table Waypoints for DCS task route or spawn template. -function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport) +function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) -- Altitude of input parameter or y-component of 3D-coordinate. local _Altitude=Altitude or Coord.y @@ -929,62 +1208,7 @@ function RAT:Waypoint(Type, Coord, Speed, Altitude, Airport) return RoutePoint end - ---- Set takeoff type. Starting cold at airport, starting hot at airport, starting at runway, starting in the air or randomly select one of the previous. --- Default is "takeoff-hot" for a start at airport with engines already running. --- @param #RAT self --- @param #string type Type can be "takeoff-cold" or "cold", "takeoff-hot" or "hot", "takeoff-runway" or "runway", "air", "random". -function RAT:SetTakeoff(type) - -- All possible types for random selection. - local types={"takeoff-cold", "takeoff-hot", "takeoff-runway"} - local _Type - if type:lower()=="takeoff-cold" or type:lower()=="cold" then - _Type="takeoff-cold" - elseif type:lower()=="takeoff-hot" or type:lower()=="hot" then - _Type="takeoff-hot" - elseif type:lower()=="takeoff-runway" or type:lower()=="runway" then - _Type="takeoff-runway" - elseif type:lower()=="air" then - --TODO: not implemented yet - _Type="air" - elseif type:lower()=="random" then - _Type=types[math.random(1, #types)] - else - _Type="takeoff-hot" - end - self.takeoff=_Type -end - ---- Orbit at a specified position at a specified alititude with a specified speed. --- @param #RAT self --- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position. --- @param #number Altitude The altitude AGL to hold the position. --- @param #number Speed The speed flying when holding the position in m/s. --- @return Dcs.DCSTasking.Task#Task DCSTask -function RAT:_TaskHolding(P1, Altitude, Speed) - local LandHeight = land.getHeight(P1) - - --TODO: Add duration of holding. Otherwise it will hold until fuel is emtpy. - - -- second point is 10 km north of P1 - --TODO: randomize P1 - local P2={} - P2.x=P1.x - P2.y=P1.y+10000 - local DCSTask = { - id = 'Orbit', - params = { - pattern = AI.Task.OrbitPattern.RACE_TRACK, - point = P1, - point2 = P2, - speed = Speed, - altitude = Altitude + LandHeight - } - } - - return DCSTask -end - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Provide information about the assigned flightplan. -- @param #RAT self @@ -1029,244 +1253,7 @@ function RAT:_Routeinfo(waypoints, comment) return total end - ---- Set the route of the AI plane. Due to DCS landing bug, this has to be done before the unit is spawned. --- @param #RAT self --- @return Wrapper.Airport#AIRBASE Departure airbase. --- @return Wrapper.Airport#AIRBASE Destination airbase. --- @return #table Table of flight plan waypoints. -function RAT:_SetRoute() - - -- unit conversions - local ft2meter=0.305 - local kmh2ms=0.278 - local FL2m=30.48 - local nm2km=1.852 - - -- DEPARTURE AIRPORT - -- Departure airport or zone. - local departure=self:_SetDeparture() - - -- Coordinates of departure point. - local Pdeparture - if self.takeoff=="air" then - -- For an air start, we take a random point within the spawn zone. - local vec2=departure:GetRandomVec2() - --Pdeparture=COORDINATE:New(vec2.x, self.aircraft.FLcruise, vec2.y) - Pdeparture=COORDINATE:NewFromVec2(vec2) - else - Pdeparture=departure:GetCoordinate() - end - - -- Height ASL of departure point. - local H_departure - if self.takeoff=="air" then - -- Departure altitude is 70% of default cruise with 30% variation and limited to 1000 m AGL (50 m for helos). - local Hmin - if self.category=="plane" then - Hmin=1000 - else - Hmin=50 - end - H_departure=self:_Randomize(self.aircraft.FLcruise*0.7, 0.3, Pdeparture.y+Hmin, self.aircraft.FLcruise) - else - H_departure=Pdeparture.y - end - - -- DESTINATION AIRPORT - -- Get all destination airports within reach and at least 10 km away from departure. - self:_GetDestinations(Pdeparture, self.mindist, self.aircraft.Reff) - - -- Pick a destination airport. - local destination=self:_SetDestination() - - -- Check that departure and destination are not the same. Should not happen due to mindist. - if destination:GetName()==departure:GetName() then - local text="Destination and departure airport are identical: "..destination:GetName().." with ID "..destination:GetID() - MESSAGE:New(text, 120):ToAll() - error(myid..text) - end - - -- Coordinates of destination airport. - local Pdestination=destination:GetCoordinate() - -- Height ASL of destination airport. - local H_destination=Pdestination.y - - -- DESCENT/HOLDING POINT - -- Get a random point between 10 and 20 km away from the destination. - local Vholding - if self.category=="plane" then - Vholding=destination:GetCoordinate():GetRandomVec2InRadius(20000, 10000) - else - -- For helos we set a distance between 500 to 1000 m. - Vholding=destination:GetCoordinate():GetRandomVec2InRadius(1000, 500) - end - -- Coordinates of the holding point. y is the land height at that point. - local Pholding=COORDINATE:NewFromVec2(Vholding) - - -- Holding point altitude. For planes between 800 and 1200 m AGL. For helos 80 to 120 m AGL. - local h_holding - if self.category=="plane" then - h_holding=1000 - else - h_holding=100 - end - h_holding=self:_Randomize(h_holding, 0.2) - - -- Distance from holding point to destination. - local d_holding=Pholding:Get2DDistance(Pdestination) - - -- GENERAL - -- heading from departure to holding point of destination - local heading=self:_Course(Pdeparture, Pholding) -- heading from departure to destination - - -- total distance between departure and holding point (+last bit to destination) - local d_total=Pdeparture:Get2DDistance(Pholding) - - -- CLIMB and DESCENT angles - -- TODO: Randomize climb/descent angles. This did not work in rad. Need to convert to deg first. - local AlphaClimb=self.aircraft.AlphaClimb --=self:_Randomize(self.aircraft.AlphaClimb, 0.1) - local AlphaDescent=self.aircraft.AlphaDescent --self:_Randomize(self.aircraft.AlphaDescent, 0.1) - - --CRUISE - -- Set min/max cruise altitudes. - local FLmax - local FLmin - local FLcruise=self.aircraft.FLcruise - if self.category=="plane" then - -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. - FLmin=math.max(H_departure, H_destination+h_holding) - -- Check if the distance between the two airports is large enough to reach the desired FL and descent again at the given climb/descent rates. - if self.takeoff=="air" then - -- This is the case where we only descent to the ground at the given descent angle. - -- TODO: should this not better be h_holding Pholding.y? - FLmax=d_total*math.tan(AlphaDescent)+H_destination - else - FLmax=self:_FLmax(AlphaClimb, AlphaDescent, d_total, H_departure) - end - -- If the route is very short we set FLmin a bit lower than FLmax. - if FLmin>FLmax then - FLmin=FLmax*0.8 - end - -- Again, if the route is too short to climb and descent, we set the default cruise alt at bit lower than the max we can reach. - if FLcruise>FLmax then - FLcruise=FLmax*0.9 - end - else - -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. - FLmin=math.max(H_departure, H_destination)+50 - FLmax=math.max(H_departure, H_destination)+1000 - end - -- Set randomized cruise altitude: default +-50% but limited to FLmin and FLmax. - FLcruise=self:_Randomize(FLcruise, 0.5, FLmin, FLmax) - -- Finally, check that we are not above 90% of service ceiling. - FLcruise=math.min(FLcruise, self.aircraft.ceiling*0.9) - - if self.takeoff=="air" then - H_departure=math.min(H_departure,FLmax) - end - - -- CLIMB - -- Height of climb relative to ASL height of departure airport. - local h_climb=FLcruise-H_departure - -- x-distance of climb part - local d_climb=math.abs(h_climb/math.tan(AlphaClimb)) - -- time of climb in seconds - local t_climb=h_climb/self.aircraft.Vclimb - - -- DESCENT - -- Height difference for descent form cruise alt to holding point. - local h_descent=FLcruise-h_holding-Pholding.y - -- x-distance of descent part - local d_descent=math.abs(h_descent/math.tan(AlphaDescent)) - - -- CRUISE - -- Distance of the cruising part. This should in principle not become negative, but can happen for very short legs. - local d_cruise=d_total-d_climb-d_descent - - -- debug message - local text=string.format("Route distances:\n") - text=text..string.format("d_climb = %6.1f km\n", d_climb/1000) - text=text..string.format("d_cruise = %6.1f km\n", d_cruise/1000) - text=text..string.format("d_descent = %6.1f km\n", d_descent/1000) - text=text..string.format("d_holding = %6.1f km\n", d_holding/1000) - text=text..string.format("d_total = %6.1f km\n", d_total/1000) - text=text..string.format("Route heights:\n") - text=text..string.format("H_departure = %6.1f m ASL\n", H_departure) - text=text..string.format("H_destination = %6.1f m ASL\n", H_destination) - text=text..string.format("h_climb = %6.1f m AGL\n", h_climb) - text=text..string.format("h_descent = %6.1f m\n", h_descent) - text=text..string.format("h_holding = %6.1f m AGL\n", h_holding) - text=text..string.format("P_holding alt = %6.1f m ASL\n", Pholding.y) - text=text..string.format("Alpha_climb = %6.1f Deg\n", math.deg(AlphaClimb)) - text=text..string.format("Alpha_descent = %6.1f Deg\n", math.deg(AlphaDescent)) - text=text..string.format("FLmin = %6.1f m ASL\n", FLmin) - text=text..string.format("FLmax = %6.1f m ASL\n", FLmax) - text=text..string.format("FLcruise = %6.1f m ASL\n", FLcruise) - text=text..string.format("Heading = %6.1f Degrees", heading) - env.info(myid..text) - if self.debug then - MESSAGE:New(text, 60):ToAll() - end - - -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). - local c0=Pdeparture - local c1=c0:Translate(d_climb, heading) - local c2=c1:Translate(d_cruise, heading) - local c3=c2:Translate(d_descent, heading) - local c3=Pholding - local c4=Pdestination - - --Convert coordinates into route waypoints. - local wp0=self:Waypoint(self.takeoff, c0, self.aircraft.Vmin, H_departure, departure) - local wp1=self:Waypoint("climb", c1, self.aircraft.Vmax, FLcruise) - local wp2=self:Waypoint("cruise", c2, self.aircraft.Vcruise, FLcruise) - --TODO: add the possibility for a holing point, i.e. we circle a bit before final approach. - --local wp3=self:Waypoint("descent", c3, self.aircraft.Vmin, h_holding) - local wp3=self:Waypoint("holding", c3, self.aircraft.Vmin, h_holding) - local wp4=self:Waypoint("landing", c4, self.aircraft.Vmin, 2, destination) - - -- set waypoints - local waypoints = {wp0, wp1, wp2, wp3, wp4} - - self:_SetMarker("Takeoff and begin of climb.", c0) - self:_SetMarker("End of climb and begin of cruise", c1) - self:_SetMarker("End of Cruise and begin of descent", c2) - self:_SetMarker("Holding Point", c3) - self:_SetMarker("Final Destination", c4) - - -- some info on the route as message - self:_Routeinfo(waypoints, "Waypoint info in set_route:") - - -- return departure, destination and waypoints - return departure, destination, waypoints - -end - ---- Calculate the max flight level for a given distance and fixed climb and descent rates. --- In other words we have a distance between two airports and want to know how high we --- can climb before we must descent again to arrive at the destination without any level/cruising part. --- @param #RAT self --- @param #number alpha Angle of climb [rad]. --- @param #number beta Angle of descent [rad]. --- @param #number d Distance between the two airports [m]. --- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible. --- @return #number Maximal flight level in meters. -function RAT:_FLmax(alpha, beta, d, h0) --- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given. - local gamma=math.rad(180)-alpha-beta - local a=d*math.sin(alpha)/math.sin(gamma) - local b=d*math.sin(beta)/math.sin(gamma) - local h1=b*math.sin(alpha) - local h2=a*math.sin(beta) - local FL2m=30.48 - -- h1 and h2 should be equal. - local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1) - text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2) - env.info(myid..text) - return b*math.sin(alpha)+h0 -end - +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Modifies the template of the group to be spawned. -- In particular, the waypoints of the group's flight plan are copied into the spawn template. @@ -1325,6 +1312,77 @@ function RAT:_ModifySpawnTemplate(waypoints) end end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Orbit at a specified position at a specified alititude with a specified speed. +-- @param #RAT self +-- @param Dcs.DCSTypes#Vec2 P1 The point to hold the position. +-- @param #number Altitude The altitude AGL to hold the position. +-- @param #number Speed The speed flying when holding the position in m/s. +-- @return Dcs.DCSTasking.Task#Task DCSTask +function RAT:_TaskHolding(P1, Altitude, Speed) + local LandHeight = land.getHeight(P1) + + --TODO: Add duration of holding. Otherwise it will hold until fuel is emtpy. + + -- second point is 10 km north of P1 + --TODO: randomize P1 + local P2={} + P2.x=P1.x + P2.y=P1.y+10000 + local DCSTask = { + id = 'Orbit', + params = { + pattern = AI.Task.OrbitPattern.RACE_TRACK, + point = P1, + point2 = P2, + speed = Speed, + altitude = Altitude + LandHeight + } + } + + return DCSTask +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Calculate the max flight level for a given distance and fixed climb and descent rates. +-- In other words we have a distance between two airports and want to know how high we +-- can climb before we must descent again to arrive at the destination without any level/cruising part. +-- @param #RAT self +-- @param #number alpha Angle of climb [rad]. +-- @param #number beta Angle of descent [rad]. +-- @param #number d Distance between the two airports [m]. +-- @param #number h0 Height [m] of departure airport. Note we implicitly assume that the height difference between departure and destination is negligible. +-- @return #number Maximal flight level in meters. +function RAT:_FLmax(alpha, beta, d, h0) +-- Solve ASA triangle for one side (d) and two adjacent angles (alpha, beta) given. + local gamma=math.rad(180)-alpha-beta + local a=d*math.sin(alpha)/math.sin(gamma) + local b=d*math.sin(beta)/math.sin(gamma) + local h1=b*math.sin(alpha) + local h2=a*math.sin(beta) + local FL2m=30.48 + -- h1 and h2 should be equal. + local text=string.format("FLmax = FL%3.0f = %6.1f m.\n", h1/FL2m, h1) + text=text..string.format("FLmax = FL%3.0f = %6.1f m.", h2/FL2m, h2) + env.info(myid..text) + return b*math.sin(alpha)+h0 +end + + +--- Test if an airport exists on the current map. +-- @param #RAT self +-- @param #string name +-- @return #boolean True if airport exsits, false otherwise. +function RAT:_AirportExists(name) + for _,airport in pairs(self.airports_map) do + if airport:GetName()==name then + return true + end + end + return false +end --- Create a table with the valid coalitions for departure and destination airports. -- @param #RAT self @@ -1429,6 +1487,7 @@ function RAT:_Randomize(value, fac, lower, upper) return r end + --- Set a marker for all on the F10 map. -- @param #RAT self -- @param #number id Index of marker. @@ -1462,4 +1521,6 @@ end --function RATCRAFT:New(name) -- local self = BASE:Inherit(self, GROUP:New(name)) --end -]] \ No newline at end of file +]] + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file