mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-10-29 16:58:06 +00:00
Warehouse 0.1.9w
This commit is contained in:
parent
5a2e1f01b4
commit
6c586d69ac
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
@ -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}.
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user