Merge branch 'develop' into FF/Develop

This commit is contained in:
funkyfranky 2018-05-07 17:52:33 +02:00
commit fc852e0386
11 changed files with 1197 additions and 637 deletions

View File

@ -1,4 +1,4 @@
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo).
--- **AI** -- (R2.3) - Models the intelligent transportation of infantry and other cargo.
--
-- ===
--
@ -12,47 +12,184 @@
-- @extends Core.Fsm#FSM_CONTROLLABLE
--- # AI\_CARGO\_TROOPS class, extends @{Core.Base@BASE}
--- # AI\_CARGO\_APC class, extends @{Core.Base#BASE}
--
-- ===
--
-- AI\_CARGO\APC brings a dynamic cargo handling capability for AI groups.
--
-- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation.
-- The AI\_CARGO\APC module uses the @{Cargo} capabilities within the MOOSE framework.
-- CARGO derived objects must be declared within the mission to make the AI\_CARGO\APC object recognize the cargo.
-- Please consult the @{Cargo} module for more information.
--
-- ## Cargo loading.
--
-- The module will load automatically cargo when the APCs are within boarding or loading range.
-- The boarding or loading range is specified when the cargo is created in the simulation, and therefore, this range depends on the type of cargo
-- and the specified boarding range.
--
-- ## Enemies nearby.
--
-- When the APCs are approaching enemy units, something special is happening.
-- The APCs will stop moving, and the loaded infantry will unboard and follow the APCs and will help to defend the group.
-- The carrier will hold the route once the unboarded infantry is further than 50 meters from the APCs,
-- to ensure that the APCs are not too far away from the following running infantry.
-- Once all enemies are cleared, the infantry will board again automatically into the APCs. Once boarded, the APCs will follow its pre-defined route.
--
-- A combat range needs to be specified in meters at the @{#AI_CARGO_APC.New}() method.
-- This combat range will trigger the unboarding of troops when enemies are within the combat range around the APCs.
-- During my tests, I've noticed that there is a balance between ensuring that the infantry is within sufficient hit range (effectiveness) versus
-- vulnerability of the infantry. It all depends on the kind of enemies that are expected to be encountered.
-- A combat range of 350 meters to 500 meters has been proven to be the most effective and efficient.
--
-- ## Infantry health.
--
-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield.
-- As a result, the unboarding infantry is very _healthy_ every time it unboards.
-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter.
-- However, infantry that was destroyed when unboarded and following the APCs, won't be respawned again. Destroyed is destroyed.
-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has
-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every
-- time is not so much of an issue ...
--
-- ## Control the APCs on the map.
--
-- It is possible also as a human ground commander to influence the path of the APCs, by pointing a new path using the DCS user interface on the map.
-- In this case, the APCs will change the direction towards its new indicated route. However, there is a catch!
-- Once the APCs are near the enemy, and infantry is unboarded, the APCs won't be able to hold the route until the infantry could catch up.
-- The APCs will simply drive on and won't stop! This is a limitation in ED that prevents user actions being controlled by the scripting engine.
-- No workaround is possible on this.
--
-- ## Cargo deployment.
--
-- Using the @{#AI_CARGO_APC.Deploy}() method, you are able to direct the APCs towards a point on the battlefield to unboard/unload the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
-- ## Cargo pickup.
--
-- Using the @{#AI_CARGO_APC.Pickup}() method, you are able to direct the APCs towards a point on the battlefield to board/load the cargo at the specific coordinate.
-- The APCs will follow nearby roads as much as possible, to ensure fast and clean cargo transportation between the objects and villages in the simulation environment.
--
--
--
-- @field #AI_CARGO_APC
AI_CARGO_APC = {
ClassName = "AI_CARGO_APC",
Coordinate = nil -- Core.Point#COORDINATE,
Coordinate = nil, -- Core.Point#COORDINATE,
APC_Cargo = {},
}
--- Creates a new AI_CARGO_APC object.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param Cargo.CargoGroup#CARGO_GROUP CargoGroup
-- @param Wrapper.Group#GROUP CargoCarrier
-- @param Core.Set#SET_CARGO CargoSet
-- @param #number CombatRadius
-- @return #AI_CARGO_APC
function AI_CARGO_APC:New( CargoCarrier, CargoGroup, CombatRadius )
function AI_CARGO_APC:New( CargoCarrier, CargoSet, CombatRadius )
local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_APC
self.CargoGroup = CargoGroup -- Cargo.CargoGroup#CARGO_GROUP
self.CargoSet = CargoSet -- Core.Set#SET_CARGO
self.CombatRadius = CombatRadius
self:SetStartState( "UnLoaded" )
self:SetStartState( "Unloaded" )
self:AddTransition( "Unloaded", "Pickup", "*" )
self:AddTransition( "Loaded", "Deploy", "*" )
self:AddTransition( "*", "Load", "Boarding" )
self:AddTransition( "Boarding", "Board", "Boarding" )
self:AddTransition( "Boarding", "Loaded", "Loaded" )
self:AddTransition( "Loaded", "Unload", "Unboarding" )
self:AddTransition( "Unboarding", "Unboard", "Unboarding" )
self:AddTransition( "Unboarding", "Unloaded", "Unloaded" )
self:AddTransition( { "Unboarding", "Unloaded" }, "Unloaded", "Unloaded" )
self:AddTransition( "*", "Monitor", "*" )
self:AddTransition( "*", "Follow", "Following" )
self:AddTransition( "*", "Guard", "Guarding" )
self:AddTransition( "*", "Guard", "Unloaded" )
self:AddTransition( "*", "Destroyed", "Destroyed" )
--- Pickup Handler OnBefore for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnBeforePickup
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Pickup Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterPickup
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] Pickup
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
--- Pickup Asynchronous Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] __Pickup
-- @param #AI_CARGO_APC self
-- @param #number Delay
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Handler OnBefore for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnBeforeDeploy
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean
--- Deploy Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterDeploy
-- @param #AI_CARGO_APC self
-- @param #string From
-- @param #string Event
-- @param #string To
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] Deploy
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
--- Deploy Asynchronous Trigger for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] __Deploy
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Delay
--- Loaded Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterLoaded
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From
-- @param #string Event
-- @param #string To
--- Unloaded Handler OnAfter for AI_CARGO_APC
-- @function [parent=#AI_CARGO_APC] OnAfterUnloaded
-- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param #string From
-- @param #string Event
-- @param #string To
self:__Monitor( 1 )
self:SetCarrier( CargoCarrier )
self.Transporting = false
return self
end
@ -60,11 +197,11 @@ end
--- Set the Carrier.
-- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param Wrapper.Group#GROUP CargoCarrier
-- @return #AI_CARGO_APC
function AI_CARGO_APC:SetCarrier( CargoCarrier )
self.CargoCarrier = CargoCarrier -- Wrapper.Unit#UNIT
self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUP
self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_APC", self )
CargoCarrier:HandleEvent( EVENTS.Dead )
@ -106,11 +243,16 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier )
end
function AI_CARGO_APC:IsTransporting()
return self.Transporting == true
end
--- Find a free Carrier within a range.
-- @param #AI_CARGO_APC self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Unit#UNIT NewCarrier
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_APC:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
@ -122,11 +264,12 @@ function AI_CARGO_APC:FindCarrier( Coordinate, Radius )
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
self:SetCarrier( NearUnit )
break
return NearUnit:GetGroup()
end
end
end
return nil
end
@ -135,18 +278,20 @@ end
--- Follow Infantry to the Carrier.
-- @param #AI_CARGO_APC self
-- @param #AI_CARGO_APC Me
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param Wrapper.Group#GROUP InfantryGroup
-- @param Wrapper.Unit#UNIT APCUnit
-- @param Cargo.CargoGroup#CARGO_GROUP Cargo
-- @return #AI_CARGO_APC
function AI_CARGO_APC:FollowToCarrier( Me, CargoCarrier, InfantryGroup )
function AI_CARGO_APC:FollowToCarrier( Me, APCUnit, CargoGroup )
local InfantryGroup = CargoGroup:GetGroup()
self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } )
--if self:Is( "Following" ) then
if CargoCarrier:IsAlive() then
if APCUnit:IsAlive() then
-- We check if the Cargo is near to the CargoCarrier.
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", CargoCarrier, 5 ) ) then
if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", APCUnit, 25 ) ) then
-- The Cargo does not need to follow the Carrier.
Me:Guard()
@ -167,12 +312,12 @@ function AI_CARGO_APC:FollowToCarrier( Me, CargoCarrier, InfantryGroup )
self:F({FromGround=FromGround})
table.insert( Waypoints, FromGround )
local ToCoord = CargoCarrier:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToCoord = APCUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 )
local ToGround = ToCoord:WaypointGround( 10, "Diamond" )
self:F({ToGround=ToGround})
table.insert( Waypoints, ToGround )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, CargoCarrier, InfantryGroup )
local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_APC.FollowToCarrier", Me, APCUnit, CargoGroup )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
@ -186,48 +331,52 @@ end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterMonitor( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterMonitor( APC, From, Event, To )
self:F( { APC, From, Event, To } )
if CargoCarrier and CargoCarrier:IsAlive() then
if APC and APC:IsAlive() then
if self.CarrierCoordinate then
local Coordinate = CargoCarrier:GetCoordinate()
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) or self:Is( "Guarding" ) or self:Is( "Following" ) then
-- There are no enemies within combat range. Load the CargoCarrier.
self:__Load( 1 )
end
else
if self:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
self:__Unload( 1 )
end
end
if self:Is( "Guarding" ) then
if not self.CargoGroup:IsNear( CargoCarrier, 5 ) then
self:Follow()
end
end
if self:Is( "Following" ) then
local Distance = Coordinate:Get2DDistance( self.CargoGroup:GetCoordinate() )
self:F( { Distance = Distance } )
if Distance > 40 then
CargoCarrier:RouteStop()
self.CarrierStopped = true
if self:IsTransporting() then
local Coordinate = APC:GetCoordinate()
self.Zone:Scan( { Object.Category.UNIT } )
if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then
if self:Is( "Unloaded" ) or self:Is( "Following" ) then
-- There are no enemies within combat range. Load the CargoCarrier.
self:Load()
end
else
if self.CarrierStopped then
if self.CargoGroup:IsNear( CargoCarrier, 10 ) then
CargoCarrier:RouteResume()
self.CarrierStopped = nil
if self:Is( "Loaded" ) then
-- There are enemies within combat range. Unload the CargoCarrier.
self:__Unload( 1 )
else
if self:Is( "Unloaded" ) then
self:Follow()
end
if self:Is( "Following" ) then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
if not Cargo:IsNear( APCUnit, 40 ) then
APCUnit:RouteStop()
self.CarrierStopped = true
else
if self.CarrierStopped then
if Cargo:IsNear( APCUnit, 25 ) then
APCUnit:RouteResume()
self.CarrierStopped = nil
end
end
end
end
end
end
end
end
end
end
self.CarrierCoordinate = CargoCarrier:GetCoordinate()
self.CarrierCoordinate = APC:GetCoordinate()
end
self:__Monitor( -5 )
@ -236,27 +385,52 @@ end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterLoad( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To )
self:F( { APC, From, Event, To } )
if CargoCarrier and CargoCarrier:IsAlive() then
CargoCarrier:RouteStop()
self:__Board( 10 )
self.CargoGroup:Board( CargoCarrier, 10 )
local Boarding = false
self.BoardingCount = 0
if APC and APC:IsAlive() then
self.APC_Cargo = {}
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded(), Cargo:GetName(), APC:GetName() } )
if Cargo:IsUnLoaded() then
if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then
self:F( { "In radius", APCUnit:GetName() } )
APC:RouteStop()
--Cargo:Ungroup()
Cargo:Board( APCUnit, 25 )
self:__Board( 1, Cargo )
Boarding = true
-- So now this APCUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.APC_Cargo[APCUnit] = Cargo
break
end
end
end
end
end
return Boarding
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterBoard( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterBoard( APC, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo } )
if CargoCarrier and CargoCarrier:IsAlive() then
self:F({ IsLoaded = self.CargoGroup:IsLoaded() } )
if not self.CargoGroup:IsLoaded() then
self:__Board( 10 )
if APC and APC:IsAlive() then
self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), APC:GetName() } )
if not Cargo:IsLoaded() then
self:__Board( 10, Cargo )
else
self:__Loaded( 1 )
end
@ -265,76 +439,199 @@ function AI_CARGO_APC:onafterBoard( CargoCarrier, From, Event, To )
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterLoaded( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To )
self:F( { APC, From, Event, To } )
if CargoCarrier and CargoCarrier:IsAlive() then
CargoCarrier:RouteResume()
local Loaded = true
if APC and APC:IsAlive() then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), APC:GetName() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
if Loaded == true then
APC:RouteResume()
end
return Loaded
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterUnload( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterUnload( APC, From, Event, To )
self:F( { APC, From, Event, To } )
if CargoCarrier and CargoCarrier:IsAlive() then
CargoCarrier:RouteStop()
self.CargoGroup:UnBoard( )
self:__Unboard( 10 )
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterUnboard( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
if CargoCarrier and CargoCarrier:IsAlive() then
if not self.CargoGroup:IsUnLoaded() then
self:__Unboard( 10 )
else
self:__Unloaded( 1 )
if APC and APC:IsAlive() then
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
APC:RouteStop()
for _, Cargo in pairs( APCUnit:GetCargo() ) do
Cargo:UnBoard()
self:__Unboard( 10, Cargo )
end
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterUnloaded( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterUnboard( APC, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo:GetName() } )
if CargoCarrier and CargoCarrier:IsAlive() then
self:Guard()
self.CargoCarrier = CargoCarrier
CargoCarrier:RouteResume()
if APC and APC:IsAlive() then
if not Cargo:IsUnLoaded() then
self:__Unboard( 10, Cargo )
else
self:__Unloaded( 1, Cargo )
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onbeforeUnloaded( APC, From, Event, To, Cargo )
self:F( { APC, From, Event, To, Cargo:GetName() } )
local AllUnloaded = true
--Cargo:Regroup()
if APC and APC:IsAlive() then
for _, APCUnit in pairs( APC:GetUnits() ) do
local APCUnit = APCUnit -- Wrapper.Unit#UNIT
local CargoCheck = self.APC_Cargo[APCUnit]
self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } )
if CargoCheck:IsUnLoaded() == false then
AllUnloaded = false
break
end
end
if AllUnloaded == true then
self:Guard()
self.CargoCarrier = APC
self.APC_Cargo = {}
end
end
self:F( { AllUnloaded = AllUnloaded } )
return AllUnloaded
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC:onafterFollow( APC, From, Event, To )
self:F( { APC, From, Event, To } )
self:F( "Follow" )
if APC and APC:IsAlive() then
for APCUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsUnLoaded() then
self:FollowToCarrier( self, APCUnit, Cargo )
APCUnit:RouteResume()
end
end
end
end
--- @param #AI_CARGO_APC
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC._Pickup( APC, self )
APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } )
if APC:IsAlive() then
self:Load()
self.Transporting = true
end
end
--- @param #AI_CARGO_APC
-- @param Wrapper.Group#GROUP APC
function AI_CARGO_APC._Deploy( APC, self )
APC:F( { "AI_CARGO_APC._Deploy:", APC } )
if APC:IsAlive() then
self:Unload()
self.Transporting = false
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
-- @param #string EndPointFormation The formation at the end point of the action.
function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, EndPointFormation )
if APC and APC:IsAlive() then
if Coordinate then
self.RoutePickup = true
local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, EndPointFormation )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
else
AI_CARGO_APC._Pickup( APC, self )
end
end
end
--- @param #AI_CARGO_APC self
-- @param Wrapper.Unit#UNIT CargoCarrier
function AI_CARGO_APC:onafterFollow( CargoCarrier, From, Event, To )
self:F( { CargoCarrier, From, Event, To } )
-- @param Wrapper.Group#GROUP APC
-- @param From
-- @param Event
-- @param To
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Speed
-- @param #string EndPointFormation The formation at the end point of the action.
function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, EndPointFormation )
self:F( "Follow" )
if CargoCarrier and CargoCarrier:IsAlive() then
self.CargoGroup.CargoSet:ForEach(
--- @param Core.Cargo#CARGO Cargo
function( Cargo )
self:F( { "Follow", Cargo.CargoObject:GetName() } )
if Cargo.CargoObject:IsAlive() == true then
self:F( { "Follow", Cargo.CargoObject:GetID() } )
self:FollowToCarrier( self, CargoCarrier, Cargo.CargoObject:GetGroup() )
end
end
)
if APC and APC:IsAlive() then
self.RouteDeploy = true
local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, EndPointFormation )
local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self )
self:F({Waypoints = Waypoints})
local Waypoint = Waypoints[#Waypoints]
APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone.
APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details.
end
end

View File

@ -24,7 +24,7 @@ AI_CARGO_HELICOPTER = {
--- Creates a new AI_CARGO_HELICOPTER object.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
-- @param Core.Set#SET_CARGO CargoSet
-- @param #number CombatRadius
-- @return #AI_CARGO_HELICOPTER
@ -116,13 +116,13 @@ end
--- Set the Carrier.
-- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
-- @return #AI_CARGO_HELICOPTER
function AI_CARGO_HELICOPTER:SetCarrier( Helicopter )
local AICargo = self
self.Helicopter = Helicopter -- Wrapper.Unit#UNIT
self.Helicopter = Helicopter -- Wrapper.Group#GROUP
self.Helicopter:SetState( self.Helicopter, "AI_CARGO_HELICOPTER", self )
self.RoutePickup = false
@ -173,7 +173,7 @@ end
-- @param #AI_CARGO_HELICOPTER self
-- @param Core.Point#COORDINATE Coordinate
-- @param #number Radius
-- @return Wrapper.Unit#UNIT NewCarrier
-- @return Wrapper.Group#GROUP NewCarrier
function AI_CARGO_HELICOPTER:FindCarrier( Coordinate, Radius )
local CoordinateZone = ZONE_RADIUS:New( "Zone" , Coordinate:GetVec2(), Radius )
@ -183,18 +183,19 @@ function AI_CARGO_HELICOPTER:FindCarrier( Coordinate, Radius )
self:F({NearUnit=NearUnit})
if not NearUnit:GetState( NearUnit, "AI_CARGO_HELICOPTER" ) then
local Attributes = NearUnit:GetDesc()
self:F({Desc=Attributes})
self:F({Attributes=Attributes})
if NearUnit:HasAttribute( "Trucks" ) then
self:SetCarrier( NearUnit )
break
return NearUnit:GetGroup()
end
end
end
return nil
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
@ -221,7 +222,7 @@ end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
@ -276,7 +277,7 @@ end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
-- @param From
-- @param Event
-- @param To
@ -331,25 +332,47 @@ end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
function AI_CARGO_HELICOPTER:onafterLoad( Helicopter, From, Event, To, Coordinate )
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeLoad( Helicopter, From, Event, To, Coordinate )
local Boarding = false
if Helicopter and Helicopter:IsAlive() then
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
if Cargo:IsInLoadRadius( Coordinate ) then
self:__Board( 5 )
Cargo:Board( Helicopter, 25 )
self.Cargo = Cargo
break
self.BoardingCount = 0
if Helicopter and Helicopter:IsAlive() then
self.Helicopter_Cargo = {}
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsUnLoaded = Cargo:IsUnLoaded() } )
if Cargo:IsUnLoaded() then
if Cargo:IsInLoadRadius( HelicopterUnit:GetCoordinate() ) then
self:F( { "In radius", HelicopterUnit:GetName() } )
--Cargo:Ungroup()
Cargo:Board( HelicopterUnit, 25 )
self:__Board( 1, Cargo )
Boarding = true
-- So now this APCUnit has Cargo that is being loaded.
-- This will be used further in the logic to follow and to check cargo status.
self.Helicopter_Cargo[HelicopterUnit] = Cargo
break
end
end
end
end
end
end
return Boarding
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To )
if Helicopter and Helicopter:IsAlive() then
@ -364,28 +387,46 @@ function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To )
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
function AI_CARGO_HELICOPTER:onafterLoaded( Helicopter, From, Event, To )
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeLoaded( Helicopter, From, Event, To )
local Loaded = true
if Helicopter and Helicopter:IsAlive() then
for HelicopterUnit, Cargo in pairs( self.APC_Cargo ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed() } )
if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then
Loaded = false
end
end
end
return Loaded
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnload( Helicopter, From, Event, To )
if Helicopter and Helicopter:IsAlive() then
self.Cargo:UnBoard()
self:__Unboard( 10 )
for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do
local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT
for _, Cargo in pairs( HelicopterUnit:GetCargo() ) do
Cargo:UnBoard()
self:__Unboard( 10, Cargo )
end
end
end
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To )
if Helicopter and Helicopter:IsAlive() then
@ -399,13 +440,32 @@ function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To )
end
--- @param #AI_CARGO_HELICOPTER self
-- @param Wrapper.Unit#UNIT Helicopter
function AI_CARGO_HELICOPTER:onafterUnloaded( Helicopter, From, Event, To )
-- @param Wrapper.Group#GROUP Helicopter
function AI_CARGO_HELICOPTER:onbeforeUnloaded( Helicopter, From, Event, To )
local AllUnloaded = true
--Cargo:Regroup()
if Helicopter and Helicopter:IsAlive() then
self.Helicopter = Helicopter
for _, CargoCheck in pairs( self.CargoSet:GetSet() ) do
local CargoCheck = CargoCheck -- Cargo.Cargo#CARGO
self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } )
if CargoCheck:IsUnLoaded() == false then
AllUnloaded = false
break
end
end
if AllUnloaded == true then
self.Helicopter = Helicopter
self.Helicopter_Cargo = {}
end
end
self:F( { AllUnloaded = AllUnloaded } )
return AllUnloaded
end

View File

@ -6,31 +6,11 @@
--
-- ===
--
-- Cargo can be of various forms, always are composed out of ONE object ( one unit or one static or one slingload crate ):
--
-- * CARGO_UNIT, represented by a @{Unit} in a singleton @{Group}: Cargo can be represented by a Unit in a Group. a CARGO_UNIT is representable...
-- * CARGO_GROUP, represented by a @{Group}. A CARGO_GROUP is reportable...
--
-- This module is still under construction, but is described above works already, and will keep working ...
--
-- ===
--
-- # Demo Missions
--
-- ### [CARGO Demo Missions source code](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master-release/CGO%20-%20Cargo)
--
-- ### [CARGO Demo Missions, only for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/CGO%20-%20Cargo)
--
-- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases)
--
-- ===
--
-- # YouTube Channel
--
-- ### [CARGO YouTube Channel](https://www.youtube.com/watch?v=tM00lTlkpYs&list=PL7ZUrU4zZUl2zUTuKrLW5RsO9zLMqUtbf)
--
-- ===
--
-- ### Author: **FlightControl**
-- ### Contributions:
--
@ -172,11 +152,31 @@ do -- CARGO
-- @field #boolean Representable This flag defines if the cargo can be represented by a DCS Unit.
-- @field #boolean Containable This flag defines if the cargo can be contained within a DCS Unit.
--- # (R2.1) CARGO class, extends @{Fsm#FSM_PROCESS}
--- # (R2.4) CARGO class, extends @{Fsm#FSM_PROCESS}
--
-- The CARGO class defines the core functions that defines a cargo object within MOOSE.
-- A cargo is a logical object defined that is available for transport, and has a life status within a simulation.
-- A cargo is a **logical object** defined that is available for transport, and has a life status within a simulation.
--
-- CARGO is not meant to be used directly by mission designers, but provides a base class for **concrete cargo implementation classes** to handle:
--
-- * Cargo **group objects**, implemented by the @{Cargo.CargoGroup#CARGO_GROUP} class.
-- * Cargo **Unit objects**, implemented by the @{Cargo.CargoUnit#CARGO_UNIT} class.
-- * Cargo **Crate objects**, implemented by the @{Cargo.CargoCrate#CARGO_CRATE} class.
-- * Cargo **Sling Load objects**, implemented by the @{Cargo.CargoSlingload#CARGO_SLINGLOAD} class.
--
-- The above cargo classes are used by the AI\_CARGO\_ classes to allow AI groups to transport cargo:
--
-- * AI Armoured Personnel Carriers to transport cargo and engage in battles, using the @{AI.AI_Cargo_APC#AI_CARGO_APC} class.
-- * AI Helicopters to transport cargo, using the @{AI.AI_Cargo_Helicopter#AI_CARGO_HELICOPTER} class.
-- * AI Planes to transport cargo, using the @{AI.AI_Cargo_Plane#AI_CARGO_PLANE} class.
-- * AI Ships is planned.
--
-- The above cargo classes are also used by the TASK\_CARGO\_ classes to allow human players to transport cargo as part of a tasking:
--
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_TRANSPORT} to transport cargo by human players.
-- * @{Tasking.Task_Cargo_Transport#TASK_CARGO_CSAR} to transport downed pilots by human players.
--
--
-- The CARGO is a state machine: it manages the different events and states of the cargo.
-- All derived classes from CARGO follow the same state machine, expose the same cargo event functions, and provide the same cargo states.
--
@ -186,32 +186,8 @@ do -- CARGO
-- * @{#CARGO.Load}( ToCarrier ): Loads the cargo into a carrier, regardless of its position.
-- * @{#CARGO.UnBoard}( ToPointVec2 ): UnBoard the cargo from a carrier. This will trigger a movement of the cargo to the option ToPointVec2.
-- * @{#CARGO.UnLoad}( ToPointVec2 ): UnLoads the cargo from a carrier.
-- * @{#CARGO.Dead}( Controllable ): The cargo is dead. The cargo process will be ended.
-- * @{#CARGO.Destroyed}( Controllable ): The cargo is dead. The cargo process will be ended.
--
-- ## CARGO States:
--
-- * **UnLoaded**: The cargo is unloaded from a carrier.
-- * **Boarding**: The cargo is currently boarding (= running) into a carrier.
-- * **Loaded**: The cargo is loaded into a carrier.
-- * **UnBoarding**: The cargo is currently unboarding (=running) from a carrier.
-- * **Dead**: The cargo is dead ...
-- * **End**: The process has come to an end.
--
-- ## CARGO state transition methods:
--
-- State transition functions can be set **by the mission designer** customizing or improving the behaviour of the state.
-- There are 2 moments when state transition methods will be called by the state machine:
--
-- * **Leaving** the state.
-- The state transition method needs to start with the name **OnLeave + the name of the state**.
-- If the state transition method returns false, then the processing of the state transition will not be done!
-- If you want to change the behaviour of the AIControllable at this event, return false,
-- but then you'll need to specify your own logic using the AIControllable!
--
-- * **Entering** the state.
-- The state transition method needs to start with the name **OnEnter + the name of the state**.
-- These state transition methods need to provide a return value, which is specified at the function description.
--
-- @field #CARGO
CARGO = {
ClassName = "CARGO",
@ -293,7 +269,41 @@ do -- CARGO
local CargoFound = _DATABASE:FindCargo( CargoName )
return CargoFound
end
end
--- Get the x position of the cargo.
-- @param #CARGO self
-- @return #number
function CARGO:GetX()
if self:IsLoaded() then
return self.CargoCarrier:GetCoordinate().x
else
return self.CargoObject:GetCoordinate().x
end
end
--- Get the y position of the cargo.
-- @param #CARGO self
-- @return #number
function CARGO:GetY()
if self:IsLoaded() then
return self.CargoCarrier:GetCoordinate().z
else
return self.CargoObject:GetCoordinate().z
end
end
--- Get the heading of the cargo.
-- @param #CARGO self
-- @return #number
function CARGO:GetHeading()
if self:IsLoaded() then
return self.CargoCarrier:GetHeading()
else
return self.CargoObject:GetHeading()
end
end
--- Check if the cargo can be Slingloaded.
-- @param #CARGO self
@ -431,7 +441,16 @@ do -- CARGO
function CARGO:IsBoarding()
return self:Is( "Boarding" )
end
--- Check if cargo is unboarding.
-- @param #CARGO self
-- @return #boolean true if unboarding
function CARGO:IsUnboarding()
return self:Is( "UnBoarding" )
end
--- Check if cargo is alive.
-- @param #CARGO self
-- @return #boolean true if unloaded
@ -566,14 +585,14 @@ do -- CARGO
--- Check if Cargo is in the LoadRadius for the Cargo to be Boarded or Loaded.
-- @param #CARGO self
-- @param Core.Point#Coordinate Coordinate
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the CargoGroup is within the loading radius.
function CARGO:IsInLoadRadius( Coordinate )
self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
self:T( Distance )
if Distance <= self.LoadRadius then
return true
@ -586,14 +605,14 @@ do -- CARGO
--- Check if the Cargo can report itself to be Boarded or Loaded.
-- @param #CARGO self
-- @param Core.Point#Coordinate Coordinate
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo can report itself.
function CARGO:IsInReportRadius( Coordinate )
self:F( { Coordinate } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
self:T( Distance )
if Distance <= self.LoadRadius then
return true
@ -606,18 +625,18 @@ do -- CARGO
--- Check if CargoCarrier is near the Cargo to be Loaded.
-- @param #CARGO self
-- @param Core.Point#POINT_VEC2 PointVec2
-- @param Core.Point#COORDINATE Coordinate
-- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision).
-- @return #boolean
function CARGO:IsNear( PointVec2, NearRadius )
function CARGO:IsNear( Coordinate, NearRadius )
--self:F( { PointVec2 = PointVec2, NearRadius = NearRadius } )
if self.CargoObject:IsAlive() then
--local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
--local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() )
--self:F( { CargoObjectName = self.CargoObject:GetName() } )
--self:F( { CargoObjectVec2 = self.CargoObject:GetVec2() } )
--self:F( { PointVec2 = PointVec2:GetVec2() } )
local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() )
local Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:F( Distance )
if Distance <= NearRadius then
@ -979,9 +998,9 @@ end
function CARGO_PACKAGE:IsNear( CargoCarrier )
self:F()
local CargoCarrierPoint = CargoCarrier:GetPointVec2()
local CargoCarrierPoint = CargoCarrier:GetCoordinate()
local Distance = CargoCarrierPoint:DistanceFromPointVec2( self.CargoCarrier:GetPointVec2() )
local Distance = CargoCarrierPoint:Get2DDistance( self.CargoCarrier:GetCoordinate() )
self:T( Distance )
if Distance <= self.NearRadius then

View File

@ -166,14 +166,14 @@ do -- CARGO_CRATE
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
-- @param #CARGO self
-- @param Core.Point#Coordinate Coordinate
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Crate is within the report radius.
function CARGO_CRATE:IsInReportRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.LoadRadius then
return true
@ -193,7 +193,7 @@ do -- CARGO_CRATE
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.NearRadius then
return true

View File

@ -53,46 +53,47 @@ do -- CARGO_GROUP
self:F( { Type, Name, LoadRadius } )
self.CargoSet = SET_CARGO:New()
self.CargoGroup = CargoGroup
self.Grouped = true
self.CargoUnitTemplate = {}
self:SetDeployed( false )
local WeightGroup = 0
self.GroupName = CargoGroup:GetName()
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( self.GroupName ) )
self.CargoGroup:Destroy()
local GroupName = CargoGroup:GetName()
self.CargoName = GroupName:match("(.*)~CARGO") or GroupName
self.CargoTemplate = UTILS.DeepCopy( _DATABASE:GetGroupTemplate( GroupName ) )
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
GroupTemplate.name = self.CargoName .. "#CARGO"
GroupTemplate.groupId = nil
CargoGroup:Destroy()
GroupTemplate.units = {}
-- We iterate through the group template and for each unit in the template, we create a new group with one unit.
for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
local GroupName = env.getValueDictByKey( GroupTemplate.name )
-- We create a new group object with one unit...
-- First we prepare the template...
GroupTemplate.name = GroupName .. "#CARGO#" .. UnitID
GroupTemplate.groupId = nil
GroupTemplate.units = {}
GroupTemplate.units[1] = UnitTemplate
local UnitName = UnitTemplate.name .. "#CARGO"
GroupTemplate.units[1].name = UnitTemplate.name .. "#CARGO"
-- Then we register the new group in the database
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
UnitTemplate.name = UnitTemplate.name .. "#CARGO"
local CargoUnitName = UnitTemplate.name
self.CargoUnitTemplate[CargoUnitName] = UnitTemplate
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
-- And we register the spawned unit as part of the CargoSet.
local Unit = UNIT:FindByName( UnitName )
local Unit = UNIT:Register( CargoUnitName )
--local WeightUnit = Unit:GetDesc().massEmpty
--WeightGroup = WeightGroup + WeightUnit
local CargoUnit = CARGO_UNIT:New( Unit, Type, UnitName, 10 )
self.CargoSet:Add( UnitName, CargoUnit )
local CargoUnit = CARGO_UNIT:New( Unit, Type, CargoUnitName, 10 )
self.CargoSet:Add( CargoUnitName, CargoUnit )
end
-- Then we register the new group in the database
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
self:SetWeight( WeightGroup )
self.CargoLimit = 10
@ -111,13 +112,108 @@ do -- CARGO_GROUP
return self
end
--- Ungroup the cargo group into individual groups with one unit.
-- This is required because by default a group will move in formation and this is really an issue for group control.
-- Therefore this method is made to be able to ungroup a group.
-- This works for ground only groups.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Ungroup()
if self.Grouped == true then
self.Grouped = false
self.CargoGroup:Destroy()
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
if CargoUnit:IsUnLoaded() then
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
--local GroupName = env.getValueDictByKey( GroupTemplate.name )
-- We create a new group object with one unit...
-- First we prepare the template...
GroupTemplate.name = self.CargoName .. "#CARGO#" .. CargoUnitName
GroupTemplate.groupId = nil
if CargoUnit:IsUnLoaded() then
GroupTemplate.units = {}
GroupTemplate.units[1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
end
-- Then we register the new group in the database
local CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
end
end
end
end
--- Regroup the cargo group into one group with multiple unit.
-- This is required because by default a group will move in formation and this is really an issue for group control.
-- Therefore this method is made to be able to regroup a group.
-- This works for ground only groups.
-- @param #CARGO_GROUP self
function CARGO_GROUP:Regroup()
self:F("Regroup")
if self.Grouped == false then
self.Grouped = true
local GroupTemplate = UTILS.DeepCopy( self.CargoTemplate )
GroupTemplate.name = self.CargoName .. "#CARGO"
GroupTemplate.groupId = nil
GroupTemplate.units = {}
for CargoUnitName, CargoUnit in pairs( self.CargoSet:GetSet() ) do
local CargoUnit = CargoUnit -- Cargo.CargoUnit#CARGO_UNIT
self:F( { CargoUnit:GetName(), UnLoaded = CargoUnit:IsUnLoaded() } )
if CargoUnit:IsUnLoaded() then
CargoUnit.CargoObject:Destroy()
GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName]
GroupTemplate.units[#GroupTemplate.units].unitId = nil
GroupTemplate.units[#GroupTemplate.units].x = CargoUnit:GetX()
GroupTemplate.units[#GroupTemplate.units].y = CargoUnit:GetY()
GroupTemplate.units[#GroupTemplate.units].heading = CargoUnit:GetHeading()
end
end
-- Then we register the new group in the database
self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID)
self:F( { "Regroup", GroupTemplate } )
-- Now we spawn the new group based on the template created.
_DATABASE:Spawn( GroupTemplate )
end
end
--- @param #CARGO_GROUP self
-- @param Core.Event#EVENTDATA EventData
function CARGO_GROUP:OnEventCargoDead( EventData )
self:I( EventData )
local Destroyed = false
local Destroyed = false
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then
if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() or self:IsUnboarding() then
Destroyed = true
for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do
local Cargo = CargoData -- #CARGO
@ -170,10 +266,10 @@ do -- CARGO_GROUP
--- Enter Loaded State.
-- @param #CARGO_GROUP self
-- @param Wrapper.Unit#UNIT CargoCarrier
-- @param #string Event
-- @param #string From
-- @param #string To
-- @param Wrapper.Unit#UNIT CargoCarrier
function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... )
--self:F( { From, Event, To, CargoCarrier, ...} )
@ -186,6 +282,7 @@ do -- CARGO_GROUP
--self.CargoObject:Destroy()
self.CargoCarrier = CargoCarrier
self.CargoCarrier:AddCargo( self )
end
@ -243,14 +340,6 @@ do -- CARGO_GROUP
end
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetCount()
return self.CargoSet:Count()
end
--- Enter UnBoarding State.
-- @param #CARGO_GROUP self
@ -273,10 +362,12 @@ do -- CARGO_GROUP
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
self.CargoSet:ForEach(
--- @param Cargo.Cargo#CARGO Cargo
function( Cargo, NearRadius )
Cargo:__UnBoard( Timer, ToPointVec2, NearRadius )
Timer = Timer + 3
if not Cargo:IsDestroyed() then
Cargo:__UnBoard( Timer, ToPointVec2, NearRadius )
Timer = Timer + 3
end
end, { NearRadius }
)
@ -306,7 +397,7 @@ do -- CARGO_GROUP
-- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2
for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do
self:T( Cargo.current )
self:T( { Cargo:GetName(), Cargo.current } )
if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then
UnBoarded = false
end
@ -361,6 +452,9 @@ do -- CARGO_GROUP
end
self.CargoCarrier:RemoveCargo( self )
self.CargoCarrier = nil
end
@ -370,8 +464,8 @@ do -- CARGO_GROUP
-- @return #nil There is no valid Cargo in the CargoGroup.
function CARGO_GROUP:GetCoordinate()
self:F()
local Cargo = self.CargoSet:GetFirst()
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
return Cargo.CargoObject:GetCoordinate()
@ -386,26 +480,46 @@ do -- CARGO_GROUP
-- @return #boolean false if the CargoGroup is dead.
function CARGO_GROUP:IsAlive()
local Alive = true
-- For each Cargo within the CargoSet, check if the Cargo is Alive.
-- When the Cargo is Loaded, the Cargo is in the CargoCarrier, so we check if the CargoCarrier is alive.
-- When the Cargo is not Loaded, the Cargo is the CargoObject, so we check if the CargoObject is alive.
self.CargoSet:ForEach(
function( Cargo )
if self:IsLoaded() then
Alive = Alive == true and Cargo.CargoCarrier:IsAlive()
else
Alive = Alive == true and Cargo.CargoObject:IsAlive()
end
end
)
return Alive
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
return Cargo ~= nil
end
--- Get the first alive Cargo Unit of the Cargo Group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetFirstAlive()
local CargoFirstAlive = nil
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
if not Cargo:IsDestroyed() then
CargoFirstAlive = Cargo
break
end
end
return CargoFirstAlive
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetCount()
return self.CargoSet:Count()
end
--- Get the amount of cargo units in the group.
-- @param #CARGO_GROUP self
-- @return #CARGO_GROUP
function CARGO_GROUP:GetGroup( Cargo )
local Cargo = Cargo or self:GetFirstAlive() -- Cargo.Cargo#CARGO
return Cargo.CargoObject:GetGroup()
end
--- Route Cargo to Coordinate and randomize locations.
-- @param #CARGO_GROUP self
-- @param Core.Point#COORDINATE Coordinate
@ -429,12 +543,16 @@ do -- CARGO_GROUP
-- @return #boolean The Cargo is near to the Carrier.
-- @return #nil The Cargo is not near to the Carrier.
function CARGO_GROUP:IsNear( CargoCarrier, NearRadius )
--self:F( {NearRadius = NearRadius } )
self:F( {NearRadius = NearRadius } )
local Cargo = self.CargoSet:GetFirst() -- #CARGO
if Cargo then
return Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius )
for _, Cargo in pairs( self.CargoSet:GetSet() ) do
local Cargo = Cargo -- Cargo.Cargo#CARGO
if Cargo:IsAlive() then
if Cargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then
self:F( "Near" )
return true
end
end
end
return nil
@ -442,22 +560,22 @@ do -- CARGO_GROUP
--- Check if Cargo Group is in the radius for the Cargo to be Boarded.
-- @param #CARGO_GROUP self
-- @param Core.Point#Coordinate Coordinate
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Group is within the load radius.
function CARGO_GROUP:IsInLoadRadius( Coordinate )
--self:F( { Coordinate } )
local Cargo = self.CargoSet:GetFirst() -- #CARGO
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
local Distance = 0
if Cargo:IsLoaded() then
Distance = Coordinate:DistanceFromPointVec2( Cargo.CargoCarrier:GetPointVec2() )
Distance = Coordinate:Get2DDistance( Cargo.CargoCarrier:GetCoordinate() )
else
Distance = Coordinate:DistanceFromPointVec2( Cargo.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
end
--self:T( Distance )
self:F( { Distance = Distance, LoadRadius = self.LoadRadius } )
if Distance <= self.LoadRadius then
return true
else
@ -477,12 +595,13 @@ do -- CARGO_GROUP
function CARGO_GROUP:IsInReportRadius( Coordinate )
--self:F( { Coordinate } )
local Cargo = self.CargoSet:GetFirst() -- #CARGO
local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO
if Cargo then
self:F( { Cargo } )
local Distance = 0
if Cargo:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( Cargo.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() )
--self:T( Distance )
if Distance <= self.LoadRadius then
return true

View File

@ -119,14 +119,14 @@ do -- CARGO_SLINGLOAD
--- Check if Cargo Crate is in the radius for the Cargo to be reported.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#Coordinate Coordinate
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Crate is within the report radius.
function CARGO_SLINGLOAD:IsInReportRadius( Coordinate )
--self:F( { Coordinate, LoadRadius = self.LoadRadius } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
if Distance <= self.LoadRadius then
return true
end
@ -138,14 +138,14 @@ do -- CARGO_SLINGLOAD
--- Check if Cargo Slingload is in the radius for the Cargo to be Boarded or Loaded.
-- @param #CARGO_SLINGLOAD self
-- @param Core.Point#Coordinate Coordinate
-- @param Core.Point#COORDINATE Coordinate
-- @return #boolean true if the Cargo Slingload is within the loading radius.
function CARGO_SLINGLOAD:IsInLoadRadius( Coordinate )
--self:F( { Coordinate } )
local Distance = 0
if self:IsUnLoaded() then
Distance = Coordinate:DistanceFromPointVec2( self.CargoObject:GetPointVec2() )
Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() )
if Distance <= self.NearRadius then
return true
end

View File

@ -49,7 +49,7 @@ do -- CARGO_UNIT
-- @return #CARGO_UNIT
function CARGO_UNIT:New( CargoUnit, Type, Name, Weight, NearRadius )
local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, Weight, NearRadius ) ) -- #CARGO_UNIT
self:F( { Type, Name, Weight, NearRadius } )
self:I( { Type, Name, Weight, NearRadius } )
self:T( CargoUnit )
self.CargoObject = CargoUnit
@ -146,13 +146,13 @@ do -- CARGO_UNIT
local Distance = 5
if From == "UnBoarding" then
if self:IsNear( ToPointVec2, NearRadius ) then
--if self:IsNear( ToPointVec2, NearRadius ) then
return true
else
--else
self:__UnBoarding( 1, ToPointVec2, NearRadius )
end
return false
--self:__UnBoarding( 1, ToPointVec2, NearRadius )
--end
--return false
end
end
@ -258,14 +258,17 @@ do -- CARGO_UNIT
local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees.
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
-- Set the CargoObject to state Green to ensure it is boarding!
self.CargoObject:OptionAlarmStateGreen()
local Points = {}
local PointStartVec2 = self.CargoObject:GetPointVec2()
Points[#Points+1] = PointStartVec2:WaypointGround( Speed )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 2 )
self:__Boarding( -1, CargoCarrier, NearRadius )
@ -295,7 +298,7 @@ do -- CARGO_UNIT
else
self:__Boarding( -1, CargoCarrier, NearRadius, ... )
self.RunCount = self.RunCount + 1
if self.RunCount >= 60 then
if self.RunCount >= 40 then
self.RunCount = 0
local Speed = 90
local Angle = 180
@ -308,12 +311,15 @@ do -- CARGO_UNIT
local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle )
local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( Distance, CargoDeployHeading )
-- Set the CargoObject to state Green to ensure it is boarding!
self.CargoObject:OptionAlarmStateGreen()
local Points = {}
local PointStartVec2 = self.CargoObject:GetPointVec2()
Points[#Points+1] = PointStartVec2:WaypointGround( Speed )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed )
Points[#Points+1] = PointStartVec2:WaypointGround( Speed, "Off road" )
Points[#Points+1] = CargoDeployPointVec2:WaypointGround( Speed, "Off road" )
local TaskRoute = self.CargoObject:TaskRoute( Points )
self.CargoObject:SetTask( TaskRoute, 0.2 )
@ -367,6 +373,7 @@ do -- CARGO_UNIT
if self.CargoObject then
self:T("Destroying")
self.CargoObject:Destroy()
--self.CargoObject:ReSpawnAt( COORDINATE:NewFromVec2( {x=0,y=0} ), 0 )
end
end

View File

@ -284,7 +284,7 @@ do -- cargo
TemplateName = env.getValueDictByKey( TemplateName )
local Cargo = TemplateName:match( "#(CARGO)" )
local Cargo = TemplateName:match( "~(CARGO)" )
return Cargo and Cargo == "CARGO"
end
@ -293,6 +293,7 @@ do -- cargo
-- @param #DATABASE self
-- @return #DATABASE self
function DATABASE:RegisterCargos()
for CargoGroupName, CargoGroup in pairs( self.GROUPS ) do
if self:IsCargo( CargoGroupName ) then
@ -300,11 +301,12 @@ do -- cargo
local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)")
local CargoName = CargoGroupName:match("(.*)~CARGO")
local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?")
local LoadRadius = CargoParam and CargoParam:match( "RR=([%a%d]+),?")
local NearRadius = CargoParam and CargoParam:match( "NR=([%a%d]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
CARGO_GROUP:New( CargoGroup, Type, Name or CargoName, LoadRadius, NearRadius )
self:F({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius )
end
end
@ -315,15 +317,17 @@ do -- cargo
local CargoName = CargoStaticName:match("(.*)~CARGO")
local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?")
local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?")
local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName
local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") )
local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") )
if Category == "SLING" then
CARGO_SLINGLOAD:New( CargoStatic, Type, Name or CargoName, LoadRadius, NearRadius )
self:F({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
else
if Category == "CRATE" then
CARGO_CRATE:New( CargoStatic, Type, Name or CargoName, LoadRadius, NearRadius )
self:F({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius})
CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius )
end
end
end

View File

@ -274,21 +274,19 @@ do -- COORDINATE
--TODO: check this to replace
--- Calculate the distance from a reference @{DCSTypes#Vec2}.
--- Calculate the distance from a reference @{#COORDINATE}.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Vec2 Vec2Reference The reference @{DCSTypes#Vec2}.
-- @return Dcs.DCSTypes#Distance The distance from the reference @{DCSTypes#Vec2} in meters.
function COORDINATE:DistanceFromVec2( Vec2Reference )
self:F2( Vec2Reference )
-- @param #COORDINATE PointVec2Reference The reference @{#COORDINATE}.
-- @return Dcs.DCSTypes#Distance The distance from the reference @{#COORDINATE} in meters.
function COORDINATE:DistanceFromPointVec2( PointVec2Reference )
self:F2( PointVec2Reference )
local Distance = ( ( Vec2Reference.x - self.x ) ^ 2 + ( Vec2Reference.y - self.z ) ^2 ) ^0.5
local Distance = ( ( PointVec2Reference.x - self.x ) ^ 2 + ( PointVec2Reference.z - self.z ) ^2 ) ^ 0.5
self:T2( Distance )
return Distance
end
--- Add a Distance in meters from the COORDINATE orthonormal plane, with the given angle, and calculate the new COORDINATE.
-- @param #COORDINATE self
-- @param Dcs.DCSTypes#Distance Distance The Distance to be added in meters.
@ -895,11 +893,13 @@ do -- COORDINATE
function COORDINATE:WaypointGround( Speed, Formation )
self:F2( { Formation, Speed } )
local RoutePoint = {}
RoutePoint.x = self.x
RoutePoint.y = self.z
RoutePoint.action = Formation or ""
--RoutePoint.formation_template = Formation and "" or nil
RoutePoint.speed = ( Speed or 20 ) / 3.6
@ -940,13 +940,17 @@ do -- COORDINATE
-- @param #COORDINATE ToCoord Coordinate of destination.
-- @return #table Table of coordinates on road.
function COORDINATE:GetPathOnRoad(ToCoord)
self:F2(ToCoord)
local path = land.findPathOnRoads("roads", self.x, self.z, ToCoord.x, ToCoord.z)
local Path={}
for i, v in ipairs(path) do
Path[#Path+1]=COORDINATE:NewFromVec2(v)
end
self:F(string.format("Number of points in Path on Road = %d", #Path))
local path = land.findPathOnRoads("roads", self.x, self.z, ToCoord.x, ToCoord.z)
Path[#Path+1]=COORDINATE:NewFromVec2(path[1])
Path[#Path+1]=COORDINATE:NewFromVec2(path[#path])
-- I've removed this stuff because it severely slows down DCS in case of paths with a lot of segments.
-- Just the beginning and the end point is sufficient.
-- for i, v in ipairs(path) do
-- self:I(v)
-- local coord=COORDINATE:NewFromVec2(v)
-- Path[#Path+1]=COORDINATE:NewFromVec2(v)
-- end
return Path
end

View File

@ -357,9 +357,11 @@ function CONTROLLABLE:SetTask( DCSTask, WaitTime )
local function SetTask( Controller, DCSTask )
if self and self:IsAlive() then
local Controller = self:_GetController()
self:I( "Before SetTask" )
Controller:setTask( DCSTask )
self:I( "After SetTask" )
else
BASE:E( DCSControllableName .. " is not alive anymore. Cannot set DCSTask " .. DCSTask )
BASE:E( { DCSControllableName .. " is not alive anymore.", DCSTask = DCSTask } )
end
end
@ -496,7 +498,7 @@ function CONTROLLABLE:SetTaskWaypoint( Waypoint, Task )
Waypoint.task = self:TaskCombo( { Task } )
self:T3( { Waypoint.task } )
self:F( { Waypoint.task } )
return Waypoint.task
end
@ -1766,351 +1768,390 @@ function CONTROLLABLE:TaskRoute( Points )
return DCSTask
end
--- (AIR + GROUND) Make the Controllable move to fly to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec2( Point, Speed )
self:F2( { Point, Speed } )
do -- Route methods
local ControllablePoint = self:GetUnit( 1 ):GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
PointTo.x = Point.x
PointTo.y = Point.y
PointTo.type = "Turning Point"
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
--- (AIR + GROUND) Make the Controllable move to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec3( Point, Speed )
self:F2( { Point, Speed } )
local ControllableVec3 = self:GetUnit( 1 ):GetVec3()
local PointFrom = {}
PointFrom.x = ControllableVec3.x
PointFrom.y = ControllableVec3.z
PointFrom.alt = ControllableVec3.y
PointFrom.alt_type = "BARO"
PointFrom.type = "Turning Point"
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
PointTo.x = Point.x
PointTo.y = Point.z
PointTo.alt = Point.y
PointTo.alt_type = "BARO"
PointTo.type = "Turning Point"
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
--- Make the controllable to follow a given route.
-- @param #CONTROLLABLE self
-- @param #table Route A table of Route Points.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:Route( Route, DelaySeconds )
self:F2( Route )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route.
self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1).
return self
end
return nil
end
--- Stops the movement of the vehicle on the route.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:RouteStop()
self:F("RouteStop")
--- (AIR + GROUND) Make the Controllable move to fly to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec2( Point, Speed )
self:F2( { Point, Speed } )
local CommandStop = self:CommandStopRoute( true )
self:SetCommand( CommandStop )
end
--- Resumes the movement of the vehicle on the route.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:RouteResume()
self:F("RouteResume")
local ControllablePoint = self:GetUnit( 1 ):GetVec2()
local CommandResume = self:CommandStopRoute( false )
self:SetCommand( CommandResume )
end
--- Make the GROUND Controllable to drive towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right".
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointGround()
local ToWP = ToCoordinate:WaypointGround( Speed, Formation )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- Make the GROUND Controllable to drive towards a specific point using (only) roads.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds )
-- Current coordinate.
local FromCoordinate = self:GetCoordinate()
-- Formation is set to on road.
local Formation="On Road"
-- Path on road from current position to destination coordinate.
local path=FromCoordinate:GetPathOnRoad(ToCoordinate)
-- Route, ground waypoints along roads.
local route={}
table.insert(route, FromCoordinate:WaypointGround(Speed, Formation))
-- Convert coordinates to ground waypoints and insert into table.
for _, coord in ipairs(path) do
table.insert(route, coord:WaypointGround(Speed, Formation))
end
-- Add the final coordinate because the final coordinate in path is last point on road.
local dist=ToCoordinate:Get2DDistance(path[#path])
if dist>10 then
table.insert(route, ToCoordinate:WaypointGround(Speed, "Vee"))
end
-- Route controllable to destination.
self:Route(route, DelaySeconds)
return self
end
--- Make the AIR Controllable fly towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type.
-- @param Core.Point#COORDINATE.RoutePointType Type The route point type.
-- @param Core.Point#COORDINATE.RoutePointAction Action The route point action.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointAir()
local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- (AIR + GROUND) Route the controllable to a given zone.
-- The controllable final destination point can be randomized.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to route to.
-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
self:F2( Zone )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
local ZonePoint
if Randomize then
ZonePoint = Zone:GetRandomVec2()
else
ZonePoint = Zone:GetVec2()
end
PointTo.x = ZonePoint.x
PointTo.y = ZonePoint.y
PointTo.x = Point.x
PointTo.y = Point.y
PointTo.type = "Turning Point"
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 20 / 1.6
end
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
return nil
end
--- (GROUND) Route the controllable to a given Vec2.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param #Vec2 Vec2 The Vec2 where to route to.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
--- (AIR + GROUND) Make the Controllable move to a given point.
-- @param #CONTROLLABLE self
-- @param Dcs.DCSTypes#Vec3 Point The destination point in Vec3 format.
-- @param #number Speed The speed to travel.
-- @return #CONTROLLABLE self
function CONTROLLABLE:RouteToVec3( Point, Speed )
self:F2( { Point, Speed } )
local ControllableVec3 = self:GetUnit( 1 ):GetVec3()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.x = ControllableVec3.x
PointFrom.y = ControllableVec3.z
PointFrom.alt = ControllableVec3.y
PointFrom.alt_type = "BARO"
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
PointFrom.action = "Turning Point"
PointFrom.speed = Speed
PointFrom.speed_locked = true
PointFrom.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local PointTo = {}
PointTo.x = Vec2.x
PointTo.y = Vec2.y
PointTo.x = Point.x
PointTo.y = Point.z
PointTo.alt = Point.y
PointTo.alt_type = "BARO"
PointTo.type = "Turning Point"
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 60 / 3.6
end
PointTo.action = "Fly Over Point"
PointTo.speed = Speed
PointTo.speed_locked = true
PointTo.properties = {
["vnav"] = 1,
["scale"] = 0,
["angle"] = 0,
["vangle"] = 0,
["steer"] = 2,
}
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
--- Make the controllable to follow a given route.
-- @param #CONTROLLABLE self
-- @param #table Route A table of Route Points.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:Route( Route, DelaySeconds )
self:F2( Route )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local RouteTask = self:TaskRoute( Route ) -- Create a RouteTask, that will route the CONTROLLABLE to the Route.
self:SetTask( RouteTask, DelaySeconds or 1 ) -- Execute the RouteTask after the specified seconds (default is 1).
return self
end
return nil
end
--- Stops the movement of the vehicle on the route.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:RouteStop()
self:F("RouteStop")
local CommandStop = self:CommandStopRoute( true )
self:SetCommand( CommandStop )
end
--- Resumes the movement of the vehicle on the route.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE
function CONTROLLABLE:RouteResume()
self:F("RouteResume")
local CommandResume = self:CommandStopRoute( false )
self:SetCommand( CommandResume )
end
--- Make the GROUND Controllable to drive towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string Formation (optional) The route point Formation, which is a text string that specifies exactly the Text in the Type of the route point, like "Vee", "Echelon Right".
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundTo( ToCoordinate, Speed, Formation, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointGround()
local ToWP = ToCoordinate:WaypointGround( Speed, Formation )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- Make the GROUND Controllable to drive towards a specific point using (only) roads.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteGroundOnRoad( ToCoordinate, Speed, DelaySeconds )
-- Current coordinate.
local FromCoordinate = self:GetCoordinate()
-- Formation is set to on road.
local Formation="On Road"
-- Path on road from current position to destination coordinate.
local path=FromCoordinate:GetPathOnRoad(ToCoordinate)
-- Route, ground waypoints along roads.
local route={}
table.insert(route, FromCoordinate:WaypointGround(Speed, Formation))
-- Convert coordinates to ground waypoints and insert into table.
for _, coord in ipairs(path) do
table.insert(route, coord:WaypointGround(Speed, Formation))
end
-- Add the final coordinate because the final coordinate in path is last point on road.
local dist=ToCoordinate:Get2DDistance(path[#path])
if dist>10 then
table.insert(route, ToCoordinate:WaypointGround(Speed, "Vee"))
end
-- Route controllable to destination.
self:Route( route, DelaySeconds )
return self
end
return nil
end
--- Make a task for a GROUND Controllable to drive towards a specific point using (only) roads.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #string EndPointFormation The formation to achieve at the end point.
-- @return Task
function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, EndPointFormation )
-- Current coordinate.
local FromCoordinate = self:GetCoordinate()
-- Formation is set to on road.
local Formation="On Road"
-- Path on road from current position to destination coordinate.
local path=FromCoordinate:GetPathOnRoad( ToCoordinate )
-- Route, ground waypoints along roads.
local Route = {}
table.insert( Route, FromCoordinate:WaypointGround( Speed, Formation ) )
-- Convert coordinates to ground waypoints and insert into table.
for _, coord in ipairs(path) do
table.insert( Route, coord:WaypointGround( Speed, Formation ) )
end
-- Add the final coordinate because the final coordinate in path is last point on road.
local dist=ToCoordinate:Get2DDistance(path[#path])
if dist>10 then
table.insert( Route, ToCoordinate:WaypointGround( Speed, EndPointFormation ) )
end
return Route
end
--- Make the AIR Controllable fly towards a specific point.
-- @param #CONTROLLABLE self
-- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to.
-- @param Core.Point#COORDINATE.RoutePointAltType AltType The altitude type.
-- @param Core.Point#COORDINATE.RoutePointType Type The route point type.
-- @param Core.Point#COORDINATE.RoutePointAction Action The route point action.
-- @param #number Speed (optional) Speed in km/h. The default speed is 999 km/h.
-- @param #number DelaySeconds Wait for the specified seconds before executing the Route.
-- @return #CONTROLLABLE The CONTROLLABLE.
function CONTROLLABLE:RouteAirTo( ToCoordinate, AltType, Type, Action, Speed, DelaySeconds )
local FromCoordinate = self:GetCoordinate()
local FromWP = FromCoordinate:WaypointAir()
local ToWP = ToCoordinate:WaypointAir( AltType, Type, Action, Speed )
self:Route( { FromWP, ToWP }, DelaySeconds )
return self
end
--- (AIR + GROUND) Route the controllable to a given zone.
-- The controllable final destination point can be randomized.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to route to.
-- @param #boolean Randomize Defines whether to target point gets randomized within the Zone.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToZone( Zone, Randomize, Speed, Formation )
self:F2( Zone )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
local PointTo = {}
local ZonePoint
if Randomize then
ZonePoint = Zone:GetRandomVec2()
else
ZonePoint = Zone:GetVec2()
end
PointTo.x = ZonePoint.x
PointTo.y = ZonePoint.y
PointTo.type = "Turning Point"
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 20 / 1.6
end
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
return nil
end
--- (GROUND) Route the controllable to a given Vec2.
-- A speed can be given in km/h.
-- A given formation can be given.
-- @param #CONTROLLABLE self
-- @param #Vec2 Vec2 The Vec2 where to route to.
-- @param #number Speed The speed.
-- @param Base#FORMATION Formation The formation string.
function CONTROLLABLE:TaskRouteToVec2( Vec2, Speed, Formation )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local ControllablePoint = self:GetVec2()
local PointFrom = {}
PointFrom.x = ControllablePoint.x
PointFrom.y = ControllablePoint.y
PointFrom.type = "Turning Point"
PointFrom.action = Formation or "Cone"
PointFrom.speed = 20 / 1.6
local PointTo = {}
PointTo.x = Vec2.x
PointTo.y = Vec2.y
PointTo.type = "Turning Point"
if Formation then
PointTo.action = Formation
else
PointTo.action = "Cone"
end
if Speed then
PointTo.speed = Speed
else
PointTo.speed = 60 / 3.6
end
local Points = { PointFrom, PointTo }
self:T3( Points )
self:Route( Points )
return self
end
return nil
end
end -- Route methods
-- Commands

View File

@ -808,6 +808,15 @@ function POSITIONABLE:AddCargo( Cargo )
return self
end
--- Get all contained cargo.
-- @param #POSITIONABLE self
-- @return #POSITIONABLE
function POSITIONABLE:GetCargo()
return self.__.Cargo
end
--- Remove cargo.
-- @param #POSITIONABLE self
-- @param Core.Cargo#CARGO Cargo