Warehouse 0.1.9w

This commit is contained in:
funkyfranky 2018-08-17 16:36:16 +02:00
parent 5a2e1f01b4
commit 6c586d69ac
6 changed files with 543 additions and 84 deletions

View File

@ -729,6 +729,20 @@ do -- COORDINATE
return nil
end
--- Returns the heading from this to another coordinate.
-- @param #COORDINATE self
-- @param #COORDINATE ToCoordinate
-- @return #number Heading in degrees.
function COORDINATE:HeadingTo(ToCoordinate)
local dz=ToCoordinate.z-self.z
local dx=ToCoordinate.x-self.x
local heading=math.deg(math.atan2(dz, dx))
if heading < 0 then
heading = 360 + heading
end
return heading
end
--- Returns the wind direction (from) and strength.
-- @param #COORDINATE self
-- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground.
@ -949,21 +963,53 @@ do -- COORDINATE
-- @param #COORDINATE.WaypointAction Action The route point action.
-- @param DCS#Speed Speed Airspeed in km/h. Default is 500 km/h.
-- @param #boolean SpeedLocked true means the speed is locked.
-- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points.
-- @param #table DCSTasks A table of DCS#Task items which are executed at the waypoint.
-- @param #string description A text description of the waypoint, which will be shown on the F10 map.
-- @return #table The route point.
function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked )
function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description )
self:F2( { AltType, Type, Action, Speed, SpeedLocked } )
-- Defaults
AltType=AltType or "RADIO"
if SpeedLocked==nil then
SpeedLocked=true
end
Speed=Speed or 500
-- Waypoint array.
local RoutePoint = {}
-- Coordinates.
RoutePoint.x = self.x
RoutePoint.y = self.z
-- Altitude.
RoutePoint.alt = self.y
RoutePoint.alt_type = AltType or "RADIO"
RoutePoint.alt_type = AltType
-- Waypoint type.
RoutePoint.type = Type or nil
RoutePoint.action = Action or nil
RoutePoint.speed = ( Speed and Speed / 3.6 ) or ( 500 / 3.6 )
RoutePoint.speed_locked = true
-- Set speed/ETA.
RoutePoint.speed = Speed/3.6
RoutePoint.speed_locked = SpeedLocked
RoutePoint.ETA=nil
RoutePoint.ETA_locked = false
-- Waypoint description.
RoutePoint.name=description
-- Airbase parameters for takeoff and landing points.
if airbase then
local AirbaseID = airbase:GetID()
local AirbaseCategory = airbase:GetDesc().category
if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then
RoutePoint.linkUnit = AirbaseID
RoutePoint.helipadId = AirbaseID
elseif AirbaseCategory == Airbase.Category.AIRDROME then
RoutePoint.airdromeId = AirbaseID
else
self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!")
end
end
-- ["task"] =
-- {
@ -976,12 +1022,11 @@ do -- COORDINATE
-- }, -- end of ["params"]
-- }, -- end of ["task"]
-- Waypoint tasks.
RoutePoint.task = {}
RoutePoint.task.id = "ComboTask"
RoutePoint.task.params = {}
RoutePoint.task.params.tasks = {}
RoutePoint.task.params.tasks = DCSTasks or {}
return RoutePoint
end

View File

@ -2459,7 +2459,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint)
local VxCruiseMin = math.min(VxCruiseMax*0.70, 166)
-- Cruise speed (randomized). Expectation value at midpoint between min and max.
local VxCruise = self:_Random_Gaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax)
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(self.aircraft.Vmax*0.90, 200)
@ -2817,7 +2817,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint)
end
-- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax.
local FLcruise=self:_Random_Gaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax)
local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax)
-- Overrule setting if user specified a flight level explicitly.
if self.FLuser then
@ -5014,38 +5014,6 @@ function RAT:_Randomize(value, fac, lower, upper)
return r
end
--- Generate Gaussian pseudo-random numbers.
-- @param #number x0 Expectation value of distribution.
-- @param #number sigma (Optional) Standard deviation. Default 10.
-- @param #number xmin (Optional) Lower cut-off value.
-- @param #number xmax (Optional) Upper cut-off value.
-- @return #number Gaussian random number.
function RAT:_Random_Gaussian(x0, sigma, xmin, xmax)
-- Standard deviation. Default 10 if not given.
sigma=sigma or 10
local r
local gotit=false
local i=0
while not gotit do
-- Uniform numbers in [0,1). We need two.
local x1=math.random()
local x2=math.random()
-- Transform to Gaussian exp(-(x-x0)²/(2*sigma²).
r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0
i=i+1
if (r>=xmin and r<=xmax) or i>100 then
gotit=true
end
end
return r
end
--- Place markers of the waypoints. Note we assume a very specific number and type of waypoints here.
-- @param #RAT self

View File

@ -140,11 +140,16 @@ WAREHOUSE = {
--- Item of the warehouse pending queue table.
-- @type WAREHOUSE.Pendingitem
-- @extends #WAREHOUSE.Queueitem
-- @field #table assetlist Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}.
-- @field #number ndelivered Number of groups delivered to destination. Is managed automatically.
-- @field #number ntransporthome Number of transports back home. Is managed automatically.
-- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. Is managed automatically.
-- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. Is managed automatically.
-- @field #table cargoassets Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}.
-- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered.
-- @field #number ndelivered Number of groups delivered to destination.
-- @field #number cargoattribute Attribute of cargo assets of type @{#WAREHOUSE.Attribute}.
-- @field #number cargocategory Category of cargo assets of type @{#WAREHOUSE.Category}.
-- @field #table transportassets Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}.
-- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups.
-- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}.
-- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}.
-- @field #number ntransporthome Number of transports back home. transportattribute
--- Descriptors enumerator describing the type of the asset in stock.
-- @type WAREHOUSE.Descriptor
@ -191,7 +196,7 @@ WAREHOUSE.TransportType = {
--- Warehouse class version.
-- @field #string version
WAREHOUSE.version="0.1.9"
WAREHOUSE.version="0.1.9w"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Warehouse todo list.
@ -272,24 +277,25 @@ function WAREHOUSE:New(warehouse, alias)
self:SetStartState("Stopped")
-- Add FSM transitions.
self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state.
self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse.
self:AddTransition("Running", "Status", "*") -- Status update in running mode. Requests are processed.
self:AddTransition("Paused", "Status", "*") -- TODO Status update in paused mode. Requests are not processed.
self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock.
self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse.
self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode.
self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier.
self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination.
self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse.
self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere.
self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests.
self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again.
self:AddTransition("*", "Stop", "Stopped") -- TODO Stop the warehouse.
self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk.
self:AddTransition("*", "Attacked", "*") -- TODO Warehouse is under attack by enemy coalitin.
self:AddTransition("*", "Captured", "*") -- TODO Warehouse was captured by another coalition.
self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets are gone and warehouse is stopped.
self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state.
self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse.
self:AddTransition("Running", "Status", "*") -- Status update in running mode. Requests are processed.
self:AddTransition("Paused", "Status", "*") -- TODO Status update in paused mode. Requests are not processed.
self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock.
self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse.
self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode.
self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier.
self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination.
self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse.
self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere.
self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests.
self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again.
self:AddTransition("*", "Stop", "Stopped") -- TODO Stop the warehouse.
self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk.
self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition.
self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated!
self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first.
self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped.
-- Pseudo Functions
@ -495,6 +501,13 @@ function WAREHOUSE:IsPaused()
return self:is("Paused")
end
--- Check if the warehouse is under attack by another coalition.
-- @param #WAREHOUSE self
-- @return #boolean If true, the warehouse is attacked.
function WAREHOUSE:IsAttacked()
return self:is("Attacked")
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM states
@ -1492,8 +1505,21 @@ end
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function WAREHOUSE:onafterAttacked(From, Event, To)
-- @param DCS#coalition.side Coalition which is attacking the warehouse.
-- @param DCS#country.id Country which is attacking the warehouse.
function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country)
self:E(self.wid..string.format("Out warehouse is under attack!"))
--TODO: Spawn all ground units in the spawnzone?
end
--- On after "Defeated" event. Warehouse defeated an attack by another coalition.
-- @param #WAREHOUSE self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function WAREHOUSE:onafterDefeated(From, Event, To)
self:E(self.wid..string.format("Attack was defeated!"))
--TODO Put all ground assets back in stock? How to remember which? Request id. Don't delete from pending?
end
--- On after "Captured" event. Warehouse has been captured by another coalition.
@ -1502,25 +1528,17 @@ end
-- @param #string Event Event.
-- @param #string To To state.
-- @param DCS#coalition.side Coalition which captured the warehouse.
function WAREHOUSE:onafterCaptured(From, Event, To, Coalition)
-- @param DCS#country.id Country which has captured the warehouse.
function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country)
self:E(self.wid..string.format("Our warehouse was captured by coalition %d!", Coalition))
--TODO: Need to get a way to get the correct country.
local Country
if Coalition==coalition.side.BLUE then
Country=country.id.USA
elseif Coalition==coalition.side.RED then
Country=country.id.USSR
else
Country=country.id.SWITZERLAND
end
-- Respawn warehouse with new coalition/country.
self.warehouse:ReSpawn(Country)
self.coalition=Coalition
self.country=Country
self.airbase=nil
self.category=-1
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -1540,6 +1558,8 @@ function WAREHOUSE:_RouteGround(Group, Coordinate, Speed)
local _speed=Speed or Group:GetSpeedMax()*0.6
-- Create task.
-- TODO: It might be necessary to ALWAYS route the group to the road connection first.
-- At the moment, the random spawn point might give another first road point which could also be a dead end like in Kobuliti(?).
local Waypoints, canroad = Group:TaskGroundOnRoad(Coordinate, _speed, "Off Road", true)
-- Task function triggering the arrived event.
@ -1630,6 +1650,9 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed)
return
end
local Waypoints,Coordinates=self:_GetFlightplan(Aircraft,self.airbase,ToAirbase)
--[[
-- Waypoints of the route.
local Points={}
@ -1660,7 +1683,11 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed)
-- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine.
Template.route.points[2] = ToWaypoint
]]
-- Set waypoints.
Template.route.points=Waypoints
-- Respawn group at the current airbase.
env.info("FF Respawn at current airbase group = "..Aircraft:GetName().." name before")
local newAC=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false)
@ -1881,8 +1908,100 @@ end
-- Helper functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Checks if the warehouse zone was conquered by antoher coalition.
-- @param #WAREHOUSE self
function WAREHOUSE:_CheckConquered()
local coord=self.zone:GetCoordinate()
local radius=self.zone:GetRadius()
-- Scan units in zone.
--TODO: need to check if scan radius does what it should!
local gotunits,_,_,units,_,_=coord:ScanObjects(radius, true, false, false)
local Nblue=0
local Nred=0
local Nneutral=0
local CountryBlue=nil
local CountryRed=nil
local CountryNeutral=nil
if gotunits then
-- Loop over all units.
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
-- Get coalition and country.
local _coalition=unit:GetCoalition()
local _country=unit:GetCountry()
if _coalition==coalition.side.BLUE then
Nblue=Nblue+1
CountryBlue=_country
elseif _coalition==coalition.side.RED then
Nred=Nred+1
CountryRed=_country
else
Nneutral=Nneutral+1
CountryNeutral=_country
end
end
end
-- Figure out the new coalition if any.
-- Condition is that only units of one coalition are within the zone.
local newcoalition=self.coalition
local newcountry=self.country
if Nblue>0 and Nred==0 and Nneutral==0 then
-- Only blue units in zone ==> Zone goes to blue.
newcoalition=coalition.side.BLUE
newcountry=CountryBlue
elseif Nblue==0 and Nred>0 and Nneutral==0 then
-- Only red units in zone ==> Zone goes to red.
newcoalition=coalition.side.RED
newcountry=CountryRed
elseif Nblue==0 and Nred==0 and Nneutral>0 then
-- Only neutral units in zone but neutrals do not attack or even capture!
--newcoalition=coalition.side.NEUTRAL
newcountry=CountryNeutral
end
-- Coalition has changed ==> warehouse was captured!
if self:IsAttacked() and newcoalition ~= self.coalition then
self:Captured(newcoalition, newcountry)
end
-- Before a warehouse can be captured, it has to be attacked.
-- That is, even if only enemy units are present it is not immediately captured in order to spawn all ground assets for defence.
if self.coalition==coalition.side.BLUE then
-- Blue warehouse is running and we have red units in the zone.
if self:IsRunning() and Nred>0 then
self:__Attacked(coalition.side.RED, CountryRed)
end
-- Blue warehouse was under attack by blue but no more blue units in zone.
if self:IsAttacked() and Nred==0 then
self:Defeated()
end
elseif self.coalition==coalition.side.RED then
-- Red Warehouse is running and we have blue units in the zone.
if self:IsRunning() and Nblue>0 then
self:__Attacked(coalition.side.BLUE, CountryBlue)
end
-- Red warehouse was under attack by blue but no more blue units in zone.
if self:IsAttacked() and Nblue==0 then
self:Defeated()
end
elseif self.coalition==coalition.side.NEUTRAL then
-- Neutrals dont attack!
end
end
--- Checks if the request can be fulfilled in general. If not, it is removed from the queue.
-- Check if departure and destination bases are of the right type.
-- Check if departure and destination bases are of the right type.
-- @param #WAREHOUSE self
-- @param #table queue The queue which is holding the requests to check.
-- @return #boolean If true, request can be executed. If false, something is not right.
@ -2655,6 +2774,249 @@ function WAREHOUSE:_PrintQueue(queue, name)
end
end
--- Make a flight plan from a departure to a destination airport.
-- @param #WAREHOUSE self
-- @param Wrapper.Group#GROUP group
-- @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 WAREHOUSE:_GetFlightplan(group,_departure,_destination)
-- Group parameters.
local Vmax=group:GetSpeedMax()/3.6
local Range=group:GetRange()
local _category=group:GetCategory()
local DCSDesc=group:GetDCSDesc()
local ceiling=DCSDesc.Hmax
local Vymax=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.70, 166)
-- 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, 200)
-- Descent speed 60% of Vmax but max 500 km/h.
local VxDescent = math.min(Vmax*0.60, 140)
-- 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.asin(VyClimb/VxClimb)
-- Descent angle in rad. Moderate 4 degrees.
local AlphaDescent=math.rad(4)
-- Expected cruise level (peak of Gaussian distribution)
local FLcruise_expect=200*RAT.unit.FL2m
--- 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 20 km away from the destination.
local Rhmin=8000
local Rhmax=20000
if _category==Group.Category.HELICOPTER then
-- For helos we set a distance between 500 to 1000 m.
Rhmin=500
Rhmax=1000
end
-- Coordinates of the holding point. y is the land height at that point.
local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin)
local Pholding=COORDINATE:NewFromVec2(Vholding)
-- AGL height of holding point.
local H_holding=Pholding.y
-- 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)
-- This is the actual height ASL of the holding point we want to fly to
local Hh_holding=H_holding+h_holding
-- Distance from holding point to final destination.
local d_holding=Pholding:Get2DDistance(Pdestination)
-- GENERAL
local heading=Pdeparture:HeadingTo(Pdestination)
local d_total=Pdeparture:Get2DDistance(Pholding)
--------------------------------------------
-- Height difference between departure and destination.
local deltaH=math.abs(H_departure-Hh_holding)
-- Slope between departure and destination.
local phi = math.atan(deltaH/d_total)
-- Adjusted climb/descent angles.
local phi_climb
local phi_descent
if (H_departure > Hh_holding) then
phi_climb=AlphaClimb+phi
phi_descent=AlphaDescent-phi
else
phi_climb=AlphaClimb-phi
phi_descent=AlphaDescent+phi
end
-- Total distance including slope.
local D_total=math.sqrt(deltaH*deltaH+d_total*d_total)
-- SSA triangle for sloped case.
local gamma=math.rad(180)-phi_climb-phi_descent
local a = D_total*math.sin(phi_climb)/math.sin(gamma)
local b = D_total*math.sin(phi_descent)/math.sin(gamma)
local hphi_max = b*math.sin(phi_climb)
local hphi_max2 = a*math.sin(phi_descent)
-- Height of triangle.
local h_max1 = b*math.sin(AlphaClimb)
local h_max2 = a*math.sin(AlphaDescent)
-- Max height relative to departure or destination.
local h_max
if (H_departure > Hh_holding) then
h_max=math.min(h_max1, h_max2)
else
h_max=math.max(h_max1, h_max2)
end
-- Max flight level 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)
-- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m.
if _category==Group.Category.HELICOPTER then
FLmin=math.max(H_departure, H_destination)+50
FLmax=math.max(H_departure, H_destination)+1000
end
-- 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_expect<FLmin then
FLcruise_expect=FLmin
end
if FLcruise_expect>FLmax 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
-- 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
-- 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
-- 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")
--wp[#wp+1]=self:_Waypoint(#wp+1, "Departure", takeoff, c[#wp+1], VxClimb, H_departure, departure)
--- Climb
local Pclimb=Pdeparture:Translate(d_climb/2, heading)
Pclimb.y=H_departure+(FLcruise-H_departure)/2
c[#c+1]=Pclimb
wp[#wp+1]=Pclimb:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxClimb, true, nil, nil, "Climb")
--wp[#wp+1]=self:_Waypoint(#wp+1, "Climb", RAT.wp.climb, c[#wp+1], VxClimb, )
--- Begin of Cruise
local Pcruise1=Pclimb:Translate(d_climb/2, heading)
Pcruise1.y=FLcruise
c[#c+1]=Pcruise1
wp[#wp+1]=Pcruise1:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Begin of Cruise")
--wp[#wp+1]=self:_Waypoint(#wp+1, "Begin of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise)
--- End of Cruise
local Pcruise2=Pcruise1:Translate(d_cruise, heading)
Pcruise2.y=FLcruise
c[#c+1]=Pcruise2
wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "End of Cruise")
--wp[#wp+1]=self:_Waypoint(#wp+1, "End of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise)
--- Descent
local Pdescent=Pcruise2:Translate(d_descent/2, heading)
Pdescent.y=FLcruise-(FLcruise-(h_holding+H_holding))/2
c[#c+1]=Pdescent
wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent")
--wp[#wp+1]=self:_Waypoint(#wp+1, "Descent", RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2)
--- 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")
--wp[#wp+1]=self:_Waypoint(#wp+1, "Holding Point", RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding)
--- Final destination.
c[#c+1]=Pdestination
wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, nil, nil, "Final Destination")
--wp[#wp+1]=self:_Waypoint(#wp+1, "Final Destination", landing, c[#wp+1], VxFinal, H_destination, destination)
return wp,c
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -591,4 +591,71 @@ function UTILS.DisplayMissionTime(duration)
local local_time=UTILS.SecondsToClock(Tnow)
local text=string.format("Time: %s - %02d:%02d", local_time, mission_time_minutes, mission_time_seconds)
MESSAGE:New(text, duration):ToAll()
end
end
--- Generate a Gaussian pseudo-random number.
-- @param #number x0 Expectation value of distribution.
-- @param #number sigma (Optional) Standard deviation. Default 10.
-- @param #number xmin (Optional) Lower cut-off value.
-- @param #number xmax (Optional) Upper cut-off value.
-- @param #number imax (Optional) Max number of tries to get a value between xmin and xmax (if specified). Default 100.
-- @return #number Gaussian random number.
function UTILS.RandomGaussian(x0, sigma, xmin, xmax, imax)
-- Standard deviation. Default 10 if not given.
sigma=sigma or 10
-- Max attempts.
imax=imax or 100
local r
local gotit=false
local i=0
while not gotit do
-- Uniform numbers in [0,1). We need two.
local x1=math.random()
local x2=math.random()
-- Transform to Gaussian exp(-(x-x0)²/(2*sigma²).
r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0
i=i+1
if (r>=xmin and r<=xmax) or i>imax then
gotit=true
end
end
return r
end
--- Randomize a value by a certain amount.
-- @param #number value The value which should be randomized
-- @param #number fac Randomization factor.
-- @param #number lower (Optional) Lower limit of the returned value.
-- @param #number upper (Optional) Upper limit of the returned value.
-- @return #number Randomized value.
-- @usage UTILS.Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation.
-- @usage UTILS.Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120.
function UTILS.Randomize(value, fac, lower, upper)
local min
if lower then
min=math.max(value-value*fac, lower)
else
min=value-value*fac
end
local max
if upper then
max=math.min(value+value*fac, upper)
else
max=value+value*fac
end
local r=math.random(min, max)
return r
end

View File

@ -1620,6 +1620,23 @@ function GROUP:InAir()
return nil
end
--- Returns the DCS descriptor table of the nth unit of the group.
-- @param #GROUP self
-- @param #number n (Optional) The number of the unit for which the dscriptor is returned.
-- @return DCS#Object.Desc The descriptor of the first unit of the group or #nil if the group does not exist any more.
function GROUP:GetDCSDesc(n)
-- Default.
n=n or 1
local unit=self:GetUnit(n)
if unit and unit:IsAlive()~=nil then
local desc=unit:GetDesc()
return desc
end
return nil
end
do -- Route methods
--- (AIR) Return the Group to an @{Wrapper.Airbase#AIRBASE}.

View File

@ -428,7 +428,7 @@ function UNIT:GetRange()
local Desc = self:GetDesc()
if Desc then
local Range = Desc.range --This is in nautical miles for some reason.
local Range = Desc.range --This is in nautical miles for some reason. But should check again!
if Range then
Range=UTILS.NMToMeters(Range)
else