From 6c586d69ac2f3832933129fbd597ab47cf14bb99 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 17 Aug 2018 16:36:16 +0200 Subject: [PATCH] Warehouse 0.1.9w --- Moose Development/Moose/Core/Point.lua | 65 ++- Moose Development/Moose/Functional/RAT.lua | 36 +- .../Moose/Functional/Warehouse.lua | 438 ++++++++++++++++-- Moose Development/Moose/Utilities/Utils.lua | 69 ++- Moose Development/Moose/Wrapper/Group.lua | 17 + Moose Development/Moose/Wrapper/Unit.lua | 2 +- 6 files changed, 543 insertions(+), 84 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ebd9237e4..d2d9884a6 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -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 diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 95faf9daf..75bfe545d 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -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 diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0ed459d58..d9eea2a1f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -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_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 + + -- 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 + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3ac8ca74e..1ccee1d39 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -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 \ No newline at end of file +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 + + + diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 96b44357d..10174dd5b 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -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}. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index e992a3ebd..303bb1fdd 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -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