diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index a69f0aa27..c1c871cdd 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -96,7 +96,7 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) self:AddTransition( "Loaded", "Deploy", "*" ) self:AddTransition( "*", "Load", "Boarding" ) - self:AddTransition( "Boarding", "Board", "Boarding" ) + self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" ) self:AddTransition( "Boarding", "Loaded", "Loaded" ) self:AddTransition( "Loaded", "Unload", "Unboarding" ) self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) @@ -139,7 +139,7 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) -- @function [parent=#AI_CARGO_APC] __Pickup -- @param #AI_CARGO_APC self -- @param #number Delay - -- @param Core.Point#COORDINATE Coordinate + -- @param Core.Point#COORDINATE Coordinate Pickup place. If not given, loading starts at the current location. -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. --- Deploy Handler OnBefore for AI_CARGO_APC @@ -170,8 +170,8 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) --- 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 + -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. @@ -194,8 +194,12 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) self:__Monitor( 1 ) - self:SetCarrier( APC ) + + for _, APCUnit in pairs( APC:GetUnits() ) do + APCUnit:SetCargoBayWeightLimit() + end + self.Transporting = false self.Relocating = false @@ -411,7 +415,6 @@ function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To ) self:F( { APC, From, Event, To } ) local Boarding = false - self.BoardingCount = 0 if APC and APC:IsAlive() then self.APC_Cargo = {} @@ -423,16 +426,25 @@ function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To ) if Cargo:IsUnLoaded() and not Cargo:IsDeployed() 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 + local CargoBayFreeWeight = APCUnit:GetCargoBayFreeWeight() + local CargoWeight = Cargo:GetWeight() + + self:F({CargoBayFreeWeight=CargoBayFreeWeight}) + + -- Only when there is space within the bay to load the next cargo item! + if CargoBayFreeWeight > CargoWeight then --and CargoBayFreeVolume > CargoVolume then + APC:RouteStop() + --Cargo:Ungroup() + Cargo:Board( APCUnit, 25 ) + self:__Board( 1, Cargo ) + + -- 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 + Boarding = true + break + end end end end @@ -458,7 +470,31 @@ function AI_CARGO_APC:onafterBoard( APC, From, Event, To, Cargo ) if not Cargo:IsLoaded() then self:__Board( 10, Cargo ) else - self:__Loaded( 1 ) + 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 + if Cargo:IsUnLoaded() then + if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then + local CargoBayFreeWeight = APCUnit:GetCargoBayFreeWeight() + local CargoWeight = Cargo:GetWeight() + + self:F({CargoBayFreeWeight=CargoBayFreeWeight}) + + -- Only when there is space within the bay to load the next cargo item! + if CargoBayFreeWeight > CargoWeight then --and CargoBayFreeVolume > CargoVolume then + Cargo:Board( APCUnit, 25 ) + self:__Board( 10, Cargo ) + -- 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 + return + end + end + end + end + end + self:__Loaded( 5, Cargo ) end end @@ -471,7 +507,7 @@ end -- @param #string Event Event. -- @param #string To To state. -- @return #boolean Cargo loaded. -function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To ) +function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To, Cargo ) self:F( { APC, From, Event, To } ) local Loaded = true @@ -484,7 +520,6 @@ function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To ) Loaded = false end end - end if Loaded == true then @@ -496,6 +531,9 @@ function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To ) end + + + --- On after Unload event. -- @param #AI_CARGO_APC self -- @param Wrapper.Group#GROUP APC @@ -511,9 +549,12 @@ function AI_CARGO_APC:onafterUnload( APC, From, Event, To, Deployed ) local APCUnit = APCUnit -- Wrapper.Unit#UNIT APC:RouteStop() for _, Cargo in pairs( APCUnit:GetCargo() ) do - Cargo:UnBoard() - self:__Unboard( 10, Cargo, Deployed ) - end + if Cargo:IsLoaded() then + Cargo:UnBoard() + Cargo:SetDeployed( true ) + self:__Unboard( 10, Cargo, Deployed ) + end + end end end @@ -534,6 +575,17 @@ function AI_CARGO_APC:onafterUnboard( APC, From, Event, To, Cargo, Deployed ) if not Cargo:IsUnLoaded() then self:__Unboard( 10, Cargo, Deployed ) else + for _, APCUnit in pairs( APC:GetUnits() ) do + local APCUnit = APCUnit -- Wrapper.Unit#UNIT + for _, Cargo in pairs( APCUnit:GetCargo() ) do + if Cargo:IsLoaded() then + Cargo:UnBoard() + Cargo:SetDeployed( true ) + self:__Unboard( 10, Cargo, Deployed ) + return + end + end + end self:__Unloaded( 1, Cargo, Deployed ) end end @@ -559,22 +611,16 @@ function AI_CARGO_APC:onbeforeUnloaded( APC, From, Event, To, Cargo, Deployed ) 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] - if CargoCheck then - self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } ) - if CargoCheck:IsUnLoaded() == false then - AllUnloaded = false - break - end + local IsEmpty = APCUnit:IsCargoEmpty() + self:I({ IsEmpty = IsEmpty }) + if not IsEmpty then + AllUnloaded = false + break end end if AllUnloaded == true then if Deployed == true then - for APCUnit, Cargo in pairs( self.APC_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - Cargo:SetDeployed( true ) - end self.APC_Cargo = {} end self:Guard() @@ -587,6 +633,21 @@ function AI_CARGO_APC:onbeforeUnloaded( APC, From, Event, To, Cargo, Deployed ) end +--- On after Unloaded event. +-- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Cargo.Cargo#CARGO Cargo Cargo object. +-- @param #boolean Deployed Cargo is deployed. +-- @return #boolean All cargo unloaded. +function AI_CARGO_APC:onafterUnloaded( APC, From, Event, To, Cargo, Deployed ) + self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } ) + + self.Transporting = false + +end --- On after Follow event. -- @param #AI_CARGO_APC self @@ -632,7 +693,6 @@ function AI_CARGO_APC._Deploy( APC, self ) if APC:IsAlive() then self:Unload( true ) - self.Transporting = false self.Relocating = false end end diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index bbbe19ed0..1d5d1e398 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -23,8 +23,8 @@ AI_CARGO_AIRPLANE = { --- Creates a new AI_CARGO_AIRPLANE object. -- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane --- @param Core.Set#SET_CARGO CargoSet +-- @param Wrapper.Group#GROUP Airplane Plane used for transportation of cargo. +-- @param Core.Set#SET_CARGO CargoSet Cargo set to be transported. -- @return #AI_CARGO_AIRPLANE function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) @@ -34,87 +34,124 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) self:SetStartState( "Unloaded" ) - self:AddTransition( "Unloaded", "Pickup", "*" ) + self:AddTransition( { "Unloaded", "Loaded" }, "Pickup", "*" ) self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "Unloaded", "Load", "Boarding" ) + self:AddTransition( { "Unloaded", "Boarding" }, "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" ) self:AddTransition( "*", "Landed", "*" ) + self:AddTransition( "*", "Home" , "*" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) --- Pickup Handler OnBefore for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforePickup -- @param #AI_CARGO_AIRPLANE self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param Wrapper.Group#GROUP Airplane Cargo transport plane. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up. + -- @param #number Speed in km/h for travelling to pickup base. -- @return #boolean --- Pickup Handler OnAfter for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterPickup -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Group#GROUP Airplane Cargo plane. -- @param #string From -- @param #string Event -- @param #string To - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up. + -- @param #number Speed in km/h for travelling to pickup base. --- Pickup Trigger for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] Pickup -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up. + -- @param #number Speed in km/h for travelling to pickup base. --- Pickup Asynchronous Trigger for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] __Pickup -- @param #AI_CARGO_AIRPLANE self - -- @param #number Delay - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param #number Delay Delay in seconds. + -- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troops are picked up. + -- @param #number Speed in km/h for travelling to pickup base. --- Deploy Handler OnBefore for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] OnBeforeDeploy -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Group#GROUP Airplane Cargo plane. -- @param #string From -- @param #string Event -- @param #string To - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed. + -- @param #number Speed Speed in km/h for travelling to deploy base. -- @return #boolean --- Deploy Handler OnAfter for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterDeploy -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Group#GROUP Airplane Cargo plane. -- @param #string From -- @param #string Event -- @param #string To - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed. + -- @param #number Speed Speed in km/h for travelling to deploy base. --- Deploy Trigger for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] Deploy -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Airbase#AIRBASE Airbase + -- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed. + -- @param #number Speed Speed in km/h for travelling to deploy base. --- Deploy Asynchronous Trigger for AI_CARGO_AIRPLANE -- @function [parent=#AI_CARGO_AIRPLANE] __Deploy -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Airbase#AIRBASE Airbase - -- @param #number Delay - + -- @param #number Delay Delay in seconds. + -- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase where troops are deployed. + -- @param #number Speed Speed in km/h for travelling to deploy base. + --- On after Loaded event, i.e. triggered when the cargo is inside the carrier. + -- @function [parent=#AI_CARGO_AIRPLANE] OnAfterLoaded + -- @param #AI_CARGO_AIRPLANE self + -- @param Wrapper.Group#GROUP Airplane Cargo plane. + -- @param From + -- @param Event + -- @param To + + -- Set carrier. self:SetCarrier( Airplane ) + Airplane:SetCargoBayWeightLimit() + + self.Relocating = true + return self end ---- Set the Carrier. +function AI_CARGO_AIRPLANE:IsTransporting() + + return self.Transporting == true +end + +function AI_CARGO_AIRPLANE:IsRelocating() + + return self.Relocating == true +end + + + +--- Set the Carrier (controllable). Also initializes events for carrier and defines the coalition. -- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane --- @return #AI_CARGO_AIRPLANE +-- @param Wrapper.Group#GROUP Airplane Transport plane. +-- @return #AI_CARGO_AIRPLANE self function AI_CARGO_AIRPLANE:SetCarrier( Airplane ) local AICargo = self @@ -155,7 +192,8 @@ function AI_CARGO_AIRPLANE:SetCarrier( Airplane ) function Airplane:OnEventEngineShutdown( EventData ) - AICargo:Landed() + AICargo.Relocating = false + AICargo:Landed( self.Airplane ) end self.Coalition = self.Airplane:GetCoalition() @@ -168,7 +206,7 @@ end --- Find a free Carrier within a range. -- @param #AI_CARGO_AIRPLANE self - -- @param Wrapper.Airbase#AIRBASE Airbase +-- @param Wrapper.Airbase#AIRBASE Airbase -- @param #number Radius -- @return Wrapper.Group#GROUP NewCarrier function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius ) @@ -190,25 +228,32 @@ function AI_CARGO_AIRPLANE:FindCarrier( Coordinate, Radius ) end ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane +--- On after "Landed" event. Called on engine shutdown and initiates the pickup mission or unloading event. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. -- @param From -- @param Event -- @param To - -- @param Wrapper.Airbase#AIRBASE Airbase --- @param #number Speed function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) - if Airplane and Airplane:IsAlive() then + self:F({Airplane, From, Event, To}) + if Airplane and Airplane:IsAlive()~=nil then + + -- Aircraft was sent to this airbase to pickup troops. Initiate loadling. if self.RoutePickup == true then - self:Load( Airplane:GetPointVec2() ) + env.info("FF load airplane "..Airplane:GetName()) + self:Load( Airplane:GetCoordinate() ) self.RoutePickup = false + self.Relocating = true end + -- Aircraft was send to this airbase to deploy troops. Initiate unloading. if self.RouteDeploy == true then self:Unload() self.RouteDeploy = false + self.Transporting = false + self.Relocating = false end end @@ -216,231 +261,344 @@ function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) end - ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane --- @param From --- @param Event --- @param To --- @param Wrapper.Airbase#AIRBASE Airbase --- @param #number Speed +--- On after "Pickup" event. Routes transport to pickup airbase. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where the troops as picked up. +-- @param #number Speed in km/h for travelling to pickup base. function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Speed ) - if Airplane and Airplane:IsAlive() then - self:Route( Airplane, Airbase, Speed ) - self.RoutePickup = true - self.Airbase = Airbase + if Airplane and Airplane:IsAlive()~=nil then + env.info("FF onafterpick aircraft alive") + + -- Get closest airbase of current position. + local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() + + env.info("FF onafterpickup closest airbase "..ClosestAirbase:GetName()) + + -- Two cases. Aircraft spawned in air or at an airbase. + if Airplane:InAir() then + self.Airbase=nil --> route will start in air + else + self.Airbase=ClosestAirbase + end + + -- Distance from closest to pickup airbase ==> we need to know if we are already at the pickup airbase. + local Dist=Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) + env.info("Distance closest to pickup airbase = "..Dist) + + if Airplane:InAir() or Dist>500 then + + env.info("FF onafterpickup routing to airbase "..ClosestAirbase:GetName()) + + -- Route aircraft to pickup airbase. + self:Route( Airplane, Airbase, Speed ) + + -- Set airbase as starting point in the next Route() call. + self.Airbase = Airbase + + -- Aircraft is on a pickup mission. + self.RoutePickup = true + + -- Unclear!? + self.Transporting = true + self.Relocating = false + else + env.info("FF onafterpick calling landed") + + -- We are already at the right airbase ==> Landed ==> triggers loading of troops. Is usually called at engine shutdown event. + self.RoutePickup=true + self:Landed() + + end + else + env.info("FF onafterpick aircraft not alive") end end - ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane --- @param From --- @param Event --- @param To --- @param Wrapper.Airbase#AIRBASE Airbase --- @param #number Speed +--- On after Depoly event. Routes plane to the airbase where the troops are deployed. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Airbase#AIRBASE Airbase Airbase where troups should be deployed. +-- @param #number Speed Speed in km/h for travelling to deploy base. function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Airbase, Speed ) - if Airplane and Airplane:IsAlive() then + if Airplane and Airplane:IsAlive()~=nil then + + -- Activate uncontrolled airplane. + if Airplane:IsAlive()==false then + Airplane:SetCommand({id = 'Start', params = {}}) + end + + -- Route to destination airbase. self:Route( Airplane, Airbase, Speed ) + + -- Aircraft is on a depoly mission. self.RouteDeploy = true + + -- Set destination airbase for next :Route() command. self.Airbase = Airbase + + self.Transporting = true + self.Relocating = false end end ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane +--- On after Load event. Checks if cargo is inside the load radius and if so starts the boarding process. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Point#COORDINATE Coordinate Place where the cargo is guided to if it is inside the load radius. function AI_CARGO_AIRPLANE:onafterLoad( Airplane, From, Event, To, Coordinate ) - if Airplane and Airplane:IsAlive() then + if Airplane and Airplane:IsAlive() ~= nil then - for _, Cargo in pairs( self.CargoSet:GetSet() ) do - if Cargo:IsInLoadRadius( Coordinate ) then - self:__Board( 5 ) - Cargo:Board( Airplane, 25 ) - self.Cargo = Cargo - break + for _,_Cargo in pairs( self.CargoSet:GetSet() ) do + self:F({_Cargo:GetName()}) + local Cargo=_Cargo --Cargo.Cargo#CARGO + local InRadius = Cargo:IsInLoadRadius( Coordinate ) + if InRadius then + + -- Is there a cargo still unloaded? + if Cargo:IsUnLoaded() == true then + + self:__Board( 5, Cargo ) + Cargo:Board( Airplane, 25 ) + break + end end + end end end ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane -function AI_CARGO_AIRPLANE:onafterBoard( Airplane, From, Event, To ) +--- On after Board event. Cargo is inside the load radius and boarding is performed. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function AI_CARGO_AIRPLANE:onafterBoard( Airplane, From, Event, To, Cargo ) if Airplane and Airplane:IsAlive() then - self:F({ IsLoaded = self.Cargo:IsLoaded() } ) - if not self.Cargo:IsLoaded() then - self:__Board( 10 ) + + self:F({ IsLoaded = Cargo:IsLoaded() } ) + + if not Cargo:IsLoaded() then + self:__Board( 10, Cargo ) else - self:__Loaded( 1 ) + -- Check if another cargo can be loaded into the airplane. + for _,_Cargo in pairs( self.CargoSet:GetSet() ) do + + self:F({_Cargo:GetName()}) + local Cargo =_Cargo --Cargo.Cargo#CARGO + + -- Is there a cargo still unloaded? + if Cargo:IsUnLoaded() == true then + + -- Only when the cargo is within load radius. + local InRadius = Cargo:IsInLoadRadius( Airplane:GetCoordinate() ) + if InRadius then + + local CargoBayFreeWeight = Airplane:GetCargoBayFreeWeight() + --local CargoBayFreeVolume = Airplane:GetCargoBayFreeVolume() + + local CargoWeight = Cargo:GetWeight() + --local CargoVolume = Cargo:GetVolume() + + -- Only when there is space within the bay to load the next cargo item! + if CargoBayFreeWeight > CargoWeight then --and CargoBayFreeVolume > CargoVolume then + + -- ok, board. + self:__Load( 5, Airplane:GetCoordinate() ) + + -- And start the boarding loop for the AI_CARGO_AIRPLANE object until the cargo is boarded. + --Cargo:Board( Airplane, 25 ) + return + end + end + end + end + self:__Loaded( 1, Cargo ) end end end ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane -function AI_CARGO_AIRPLANE:onafterLoaded( Airplane, From, Event, To ) +--- On after Loaded event. Cargo is inside the carrier and ready to be transported. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function AI_CARGO_AIRPLANE:onafterLoaded( Airplane, From, Event, To, Cargo ) - if Airplane and Airplane:IsAlive() then - end + env.info("FF troops loaded into cargo plane") + if Airplane and Airplane:IsAlive() then + self:F( { "Transporting" } ) + self.Transporting = true -- This will only be executed when there is no cargo boarded anymore. The dispatcher will then kick-off the deploy cycle! + end end ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane +--- On after Unload event. Cargo is beeing unloaded, i.e. the unboarding process is started. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To ) if Airplane and Airplane:IsAlive() then - self.Cargo:UnBoard() - self:__Unboard( 10 ) - end - -end - ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane -function AI_CARGO_AIRPLANE:onafterUnboard( Airplane, From, Event, To ) - - if Airplane and Airplane:IsAlive() then - if not self.Cargo:IsUnLoaded() then - self:__Unboard( 10 ) - else - self:__Unloaded( 1 ) + local Cargos = Airplane:GetCargo() + for CargoID, Cargo in pairs( Cargos ) do + + local Angle = 180 + local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading ) + + Cargo:UnBoard( CargoDeployCoordinate ) + Cargo:SetDeployed( true ) + self:__Unboard( 10, Cargo ) end end end ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane -function AI_CARGO_AIRPLANE:onafterUnloaded( Airplane, From, Event, To ) +--- On after Unboard event. Checks if unboarding process is finished. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function AI_CARGO_AIRPLANE:onafterUnboard( Airplane, From, Event, To, Cargo ) + + self:E( { "Unboard", Cargo } ) + + if Airplane and Airplane:IsAlive() then + if not Cargo:IsUnLoaded() then + self:__Unboard( 10, Cargo ) + else + local Cargos = Airplane:GetCargo() + for CargoID, Cargo in pairs( Cargos ) do + if Cargo:IsLoaded() then + local Angle = 180 + local CargoCarrierHeading = Airplane:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + self:T( { CargoCarrierHeading, CargoDeployHeading } ) + local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading ) + Cargo:UnBoard( CargoDeployCoordinate ) + Cargo:SetDeployed( true ) + + self:__Unboard( 10, Cargo ) + return + end + end + self:__Unloaded( 1, Cargo ) + end + end +end + +--- On after Unloaded event. Cargo has been unloaded, i.e. the unboarding process is finished. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Cargo transport plane. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Cargo.Cargo#CARGO Cargo +function AI_CARGO_AIRPLANE:onafterUnloaded( Airplane, From, Event, To, Cargo ) + + self:E( { "Unloaded", Cargo } ) if Airplane and Airplane:IsAlive() then self.Airplane = Airplane + self.Transporting = false -- This will only be executed when there is no cargo onboard anymore. The dispatcher will then kick-off the pickup cycle! end - end +--- Route the airplane from one airport or it's current position to another airbase. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed. +-- @param Wrapper.Airbase#AIRBASE Airbase Destination airbase. +-- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do. +-- @param #boolean Uncontrolled If true, spawn group in uncontrolled state. +function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Uncontrolled ) ---- @param #AI_CARGO_AIRPLANE self --- @param Wrapper.Group#GROUP Airplane --- @param Wrapper.Airbase#AIRBASE Airbase --- @param #number Speed -function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed ) + if Airplane and Airplane:IsAlive()~=nil then - if Airplane and Airplane:IsAlive() then - - local PointVec3 = Airplane:GetPointVec3() - - local Takeoff = SPAWN.Takeoff.Hot + -- Set takeoff type. + local Takeoff = SPAWN.Takeoff.Cold + -- Get template of group. local Template = Airplane:GetTemplate() - - if Template then - - local Points = {} - - if self.Airbase then - - local FromWaypoint = Template.route.points[1] - -- These are only for ships. - FromWaypoint.linkUnit = nil - FromWaypoint.helipadId = nil - FromWaypoint.airdromeId = nil - - local AirbaseID = self.Airbase:GetID() - local AirbaseCategory = self.Airbase:GetDesc().category - - FromWaypoint.airdromeId = AirbaseID - - FromWaypoint.alt = 0 - - FromWaypoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type - FromWaypoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action - - - -- Translate the position of the Group Template to the Vec3. - for UnitID = 1, #Template.units do - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. Template.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. Template.units[UnitID].y ) - - -- These cause a lot of confusion. - local UnitTemplate = Template.units[UnitID] - - UnitTemplate.parking = 15 - UnitTemplate.parking_id = "1" - UnitTemplate.alt = 0 - - local SX = UnitTemplate.x - local SY = UnitTemplate.y - local BX = FromWaypoint.x - local BY = FromWaypoint.y - local TX = PointVec3.x + ( SX - BX ) - local TY = PointVec3.z + ( SY - BY ) - - UnitTemplate.x = TX - UnitTemplate.y = TY - - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - end - - FromWaypoint.x = PointVec3.x - FromWaypoint.y = PointVec3.z - - Points[#Points+1] = FromWaypoint - else - - local GroupPoint = Airplane:GetVec2() - local GroupVelocity = Airplane:GetUnit(1):GetDesc().speedMax - - local FromWaypoint = {} - FromWaypoint.x = GroupPoint.x - FromWaypoint.y = GroupPoint.y - FromWaypoint.type = "Turning Point" - FromWaypoint.action = "Turning Point" - FromWaypoint.speed = GroupVelocity - - Points[#Points+1] = FromWaypoint - end - - local AirbasePointVec2 = Airbase:GetPointVec2() - local ToWaypoint = AirbasePointVec2:WaypointAir( - POINT_VEC3.RoutePointAltType.BARO, - "Land", - "Landing", - Speed or Airplane:GetUnit(1):GetDesc().speedMax - ) - - ToWaypoint["airdromeId"] = Airbase:GetID() - ToWaypoint["speed_locked"] = true, - - self:F( ToWaypoint ) - - Points[#Points+1] = ToWaypoint - - Template.x = PointVec3.x - Template.y = PointVec3.z - - self:T3( Points ) - Template.route.points = Points - - --self:Respawn( Template ) - - local GroupSpawned = Airplane:Respawn( Template ) - - return GroupSpawned + -- Nil check + if Template==nil then + return end - end + -- Waypoints of the route. + local Points={} + + -- To point. + local AirbasePointVec2 = Airbase:GetPointVec2() + local ToWaypoint = AirbasePointVec2:WaypointAir( + POINT_VEC3.RoutePointAltType.BARO, + "Land", + "Landing", + Speed or Airplane:GetSpeedMax()*0.8 + ) + ToWaypoint["airdromeId"] = Airbase:GetID() + ToWaypoint["speed_locked"] = true + + + -- If self.Airbase~=nil then group is currently at an airbase, where it should be respawned. + if self.Airbase then + + -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. + Template.route.points[2] = ToWaypoint + + -- Respawn group at the current airbase. + Airplane:RespawnAtCurrentAirbase(Template, Takeoff, Uncontrolled) + + else + -- From point. + local GroupPoint = Airplane:GetVec2() + local FromWaypoint = {} + FromWaypoint.x = GroupPoint.x + FromWaypoint.y = GroupPoint.y + FromWaypoint.type = "Turning Point" + FromWaypoint.action = "Turning Point" + FromWaypoint.speed = Airplane:GetSpeedMax()*0.8 + + -- The two route points. + Points[1] = FromWaypoint + Points[2] = ToWaypoint + + local PointVec3 = Airplane:GetPointVec3() + Template.x = PointVec3.x + Template.y = PointVec3.z + + Template.route.points = Points + + local GroupSpawned = Airplane:Respawn(Template) + + end + end end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 0b1700e04..602868607 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -74,7 +74,7 @@ AI_CARGO_DISPATCHER = { ClassName = "AI_CARGO_DISPATCHER", SetCarrier = nil, - SetDeployZones = nil, + DeployZonesSet = nil, AI_Cargo = {}, PickupCargo = {} } @@ -90,23 +90,21 @@ AI_CARGO_DISPATCHER.PickupCargo = {} -- @param #AI_CARGO_DISPATCHER self -- @param Core.Set#SET_GROUP SetCarrier -- @param Core.Set#SET_CARGO SetCargo --- @param Core.Set#SET_ZONE SetDeployZones -- @return #AI_CARGO_DISPATCHER -- @usage -- -- -- Create a new cargo dispatcher --- SetCarrier = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- SetCarriers = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() +-- SetCargos = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() -- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo ) -- -function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZones ) +function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo ) local self = BASE:Inherit( self, FSM:New() ) -- #AI_CARGO_DISPATCHER self.SetCarrier = SetCarrier -- Core.Set#SET_GROUP self.SetCargo = SetCargo -- Core.Set#SET_CARGO - self.SetDeployZones = SetDeployZones -- Core.Set#SET_ZONE self:SetStartState( "Idle" ) @@ -144,6 +142,58 @@ function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZones ) end +--- Creates a new AI_CARGO_DISPATCHER object. +-- @param #AI_CARGO_DISPATCHER self +-- @param Core.Set#SET_GROUP SetCarrier +-- @param Core.Set#SET_CARGO SetCargo +-- @param Core.Set#SET_ZONE DeployZonesSet +-- @return #AI_CARGO_DISPATCHER +-- @usage +-- +-- -- Create a new cargo dispatcher +-- SetCarriers = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() +-- SetCargos = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- DeployZonesSet = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) +-- +function AI_CARGO_DISPATCHER:NewWithZones( SetCarriers, SetCargos, DeployZonesSet ) + + local self = AI_CARGO_DISPATCHER:New( SetCarriers, SetCargos ) -- #AI_CARGO_DISPATCHER + + self.DeployZonesSet = DeployZonesSet + + return self +end + + +--- Creates a new AI_CARGO_DISPATCHER object. +-- @param #AI_CARGO_DISPATCHER self +-- @param Core.Set#SET_GROUP SetCarrier +-- @param Core.Set#SET_CARGO SetCargo +-- @param Core.Set#SET_AIRBASE PickupAirbasesSet +-- @param Core.Set#SET_AIRBASE DeployAirbasesSet +-- @return #AI_CARGO_DISPATCHER +-- @usage +-- +-- -- Create a new cargo dispatcher +-- SetCarriers = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() +-- SetCargos = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- PickupAirbasesSet = SET_AIRBASES:New() +-- DeployAirbasesSet = SET_AIRBASES:New() +-- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, PickupAirbasesSet, DeployAirbasesSet ) +-- +function AI_CARGO_DISPATCHER:NewWithAirbases( SetCarriers, SetCargos, PickupAirbasesSet, DeployAirbasesSet ) + + local self = AI_CARGO_DISPATCHER:New( SetCarriers, SetCargos ) -- #AI_CARGO_DISPATCHER + + self.DeployAirbasesSet = DeployAirbasesSet + self.PickupAirbasesSet = PickupAirbasesSet + + return self +end + + + --- Set the home zone. -- When there is nothing anymore to pickup, the carriers will go to a random coordinate in this zone. -- They will await here new orders. @@ -166,6 +216,31 @@ function AI_CARGO_DISPATCHER:SetHomeZone( HomeZone ) return self end +--- Set the home airbase. This is for air units, i.e. helicopters and airplanes. +-- When there is nothing anymore to pickup, the carriers will go back to their home base. They will await here new orders. +-- @param #AI_CARGO_DISPATCHER self +-- @param Wrapper.Airbase#AIRBASE HomeBase Airbase where the carriers will go after all pickup assignments are done. +-- @return #AI_CARGO_DISPATCHER self +function AI_CARGO_DISPATCHER:SetHomeBase( HomeBase ) + + self.HomeBase = HomeBase + + return self +end + + +--- Set the home base. +-- When there is nothing anymore to pickup, the carriers will return to their home airbase. There they will await new orders. +-- @param #AI_CARGO_DISPATCHER self +-- @param Wrapper.Airbase#AIRBASE HomeBase The airbase where the carrier will go to, once they completed all pending assignments. +-- @return #AI_CARGO_DISPATCHER self +function AI_CARGO_DISPATCHER:SetHomeBase( HomeBase ) + + self.HomeBase = HomeBase + + return self +end + --- Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius. -- This radius is influencing the location where the carrier will land to pickup the cargo. @@ -328,8 +403,9 @@ function AI_CARGO_DISPATCHER:onafterMonitor() -- The Pickup sequence ... -- Check if this Carrier need to go and Pickup something... - self:I( { IsTransporting = AI_Cargo:IsTransporting() } ) - if AI_Cargo:IsTransporting() == false then + -- So, if the cargo bay is not full yet with cargo to be loaded ... + self:I( { IsRelocating = AI_Cargo:IsRelocating(), IsTransporting = AI_Cargo:IsTransporting() } ) + if AI_Cargo:IsRelocating() == false and AI_Cargo:IsTransporting() == false then -- ok, so there is a free Carrier -- now find the first cargo that is Unloaded @@ -338,7 +414,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor() for CargoName, Cargo in pairs( self.SetCargo:GetSet() ) do local Cargo = Cargo -- Cargo.Cargo#CARGO self:F( { Cargo = Cargo:GetName(), UnLoaded = Cargo:IsUnLoaded(), Deployed = Cargo:IsDeployed(), PickupCargo = self.PickupCargo[Carrier] ~= nil } ) - if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then + if Cargo:IsUnLoaded() == true and Cargo:IsDeployed() == false then local CargoCoordinate = Cargo:GetCoordinate() local CoordinateFree = true for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do @@ -358,10 +434,20 @@ function AI_CARGO_DISPATCHER:onafterMonitor() end end end + if PickupCargo then self.CarrierHome[Carrier] = nil local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) - AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ) ) + + if self.PickupAirbasesSet then + -- Find airbase within 2km from the cargo with the set. + local PickupAirbase = self.PickupAirbasesSet:FindAirbaseInRange( PickupCoordinate, 4000 ) + if PickupAirbase then + AI_Cargo:Pickup( PickupAirbase, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ) ) + end + else + AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ) ) + end break else if self.HomeZone then @@ -464,14 +550,23 @@ end -- @return #AI_CARGO_DISPATCHER function AI_CARGO_DISPATCHER:OnAfterLoaded( From, Event, To, Carrier, Cargo ) - local DeployZone = self.SetDeployZones:GetRandomZone() + if self.DeployZonesSet then - local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius ) - self.AI_Cargo[Carrier]:Deploy( DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ) ) + local DeployZone = self.DeployZonesSet:GetRandomZone() + + local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius ) + self.AI_Cargo[Carrier]:Deploy( DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ) ) - self.PickupCargo[Carrier] = nil + end + + if self.DeployAirbasesSet then + + if self.AI_Cargo[Carrier]:IsTransporting() == true then + local DeployAirbase = self.DeployAirbasesSet:GetRandomAirbase() + self.AI_Cargo[Carrier]:Deploy( DeployAirbase, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ) ) + end + end + + self.PickupCargo[Carrier] = nil end - - - diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index 561125786..1612ce2c7 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua @@ -103,7 +103,7 @@ AI_CARGO_DISPATCHER_APC = { -- function AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, CombatRadius ) - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAPC, SetCargo, SetDeployZone ) ) -- #AI_CARGO_DISPATCHER_APC + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( SetAPC, SetCargo, SetDeployZone ) ) -- #AI_CARGO_DISPATCHER_APC self.CombatRadius = CombatRadius or 500 diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua index c208bab9a..300fef9ae 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua @@ -16,8 +16,8 @@ --- Brings a dynamic cargo handling capability for AI groups. -- -- Airplanes can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI\_CARGO\_DISPATCHER\_AIRPLANE module uses the @{Cargo} capabilities within the MOOSE framework. --- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_AIRPLANE object recognize the cargo. +-- The AI_CARGO_DISPATCHER_AIRPLANE module uses the @{Cargo} capabilities within the MOOSE framework. +-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_AIRPLANE object recognize the cargo. -- Please consult the @{Cargo} module for more information. -- -- @@ -29,23 +29,33 @@ AI_CARGO_DISPATCHER_AIRPLANE = { --- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object. -- @param #AI_CARGO_DISPATCHER_AIRPLANE self --- @param Core.Set#SET_GROUP SetAirplane --- @param Core.Set#SET_CARGO SetCargo --- @param Core.Set#SET_ZONE SetDeployZone --- @return #AI_CARGO_DISPATCHER_AIRPLANE +-- @param Core.Set#SET_GROUP SetAirplanes Set of cargo transport airplanes. +-- @param Core.Set#SET_CARGO SetCargos Set of cargo, which is supposed to be transported. +-- @param Core.Set#SET_AIRBASE PickupAirbasesSet Set of airbases where the cargo has to be picked up. +-- @param Core.Set#SET_AIRBASE DeployAirbasesSet Set of airbases where the cargo is deployed. Choice for each cargo is random. +-- @return #AI_CARGO_DISPATCHER_AIRPLANE self -- @usage -- -- -- Create a new cargo dispatcher --- SetAirplane = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() --- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- AICargoDispatcher = AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo ) +-- SetAirplanes = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() +-- SetCargos = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- PickupAirbasesSet = SET_AIRBASE:New() +-- DeployAirbasesSet = SET_AIRBASE:New() +-- AICargoDispatcher = AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplanes, SetCargos, PickupAirbasesSet, DeployAirbasesSet ) -- -function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplane, SetCargo, SetDeployZones ) +function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplanes, SetCargos, PickupAirbasesSet, DeployAirbasesSet ) - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetAirplane, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithAirbases( SetAirplanes, SetCargos, PickupAirbasesSet, DeployAirbasesSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE + + self:SetDeploySpeed( 200, 150 ) + self:SetPickupSpeed( 200, 150 ) + self:SetPickupRadius( 0, 0 ) + self:SetDeployRadius( 0, 0 ) return self end +function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, SetCargo ) + return AI_CARGO_AIRPLANE:New( Airplane, SetCargo ) +end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua index eaf9666d2..26b5d9aab 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua @@ -102,7 +102,7 @@ AI_CARGO_DISPATCHER_HELICOPTER = { -- function AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo, SetDeployZones ) - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( SetHelicopter, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( SetHelicopter, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER self:SetDeploySpeed( 200, 150 ) self:SetPickupSpeed( 200, 150 ) diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 7a272178a..0148e9c79 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -20,7 +20,8 @@ -- @field #AI_CARGO_HELICOPTER AI_CARGO_HELICOPTER = { ClassName = "AI_CARGO_HELICOPTER", - Coordinate = nil -- Core.Point#COORDINATE, + Coordinate = nil, -- Core.Point#COORDINATE, + Helicopter_Cargo = {}, } AI_CARGO_QUEUE = {} @@ -43,7 +44,7 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "Unloaded", "Pickup", "*" ) self:AddTransition( "Loaded", "Deploy", "*" ) - self:AddTransition( "Unloaded", "Load", "Boarding" ) + self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" ) self:AddTransition( "Boarding", "Board", "Boarding" ) self:AddTransition( "Boarding", "Loaded", "Loaded" ) self:AddTransition( "Loaded", "Unload", "Unboarding" ) @@ -73,17 +74,20 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Pickup Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Pickup -- @param #AI_CARGO_HELICOPTER self -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Pickup Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Pickup -- @param #AI_CARGO_HELICOPTER self - -- @param #number Delay + -- @param #number Delay Delay in seconds. -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h to go to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Deploy Handler OnBefore for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] OnBeforeDeploy @@ -91,7 +95,8 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string From -- @param #string Event -- @param #string To - -- @param Core.Point#COORDINATE Coordinate + -- @param Core.Point#COORDINATE Coordinate Place at which cargo is deployed. + -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. -- @return #boolean --- Deploy Handler OnAfter for AI_CARGO_HELICOPTER @@ -101,18 +106,21 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) -- @param #string Event -- @param #string To -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Deploy Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] Deploy -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate + -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. + -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. --- Deploy Asynchronous Trigger for AI_CARGO_HELICOPTER -- @function [parent=#AI_CARGO_HELICOPTER] __Deploy + -- @param #number Delay Delay in seconds. -- @param #AI_CARGO_HELICOPTER self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Delay - + -- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. + -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. + -- We need to capture the Crash events for the helicopters. -- The helicopter reference is used in the semaphore AI_CARGO_QUEUE. @@ -137,6 +145,13 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) end ) + for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do + HelicopterUnit:SetCargoBayWeightLimit() + end + + self.Relocating = false + self.Transporting = false + self:SetCarrier( Helicopter ) return self @@ -217,7 +232,8 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) if self.RoutePickup == true then if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then - self:Load( Helicopter:GetPointVec2() ) + --self:Load( Helicopter:GetPointVec2() ) + self:Load() self.RoutePickup = false self.Relocating = true end @@ -372,24 +388,30 @@ function AI_CARGO_HELICOPTER:onbeforeLoad( Helicopter, From, Event, To) 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:IsUnLoaded() and not Cargo:IsDeployed() 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 + local CargoBayFreeWeight = HelicopterUnit:GetCargoBayFreeWeight() + local CargoWeight = Cargo:GetWeight() + + self:F({CargoBayFreeWeight=CargoBayFreeWeight}) + + -- Only when there is space within the bay to load the next cargo item! + if CargoBayFreeWeight > CargoWeight then --and CargoBayFreeVolume > CargoVolume then + + --Cargo:Ungroup() + Cargo:Board( HelicopterUnit, 25 ) + self:__Board( 1, Cargo ) + self.Helicopter_Cargo[HelicopterUnit] = Cargo + Boarding = true + break + end end end end @@ -416,13 +438,65 @@ function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To, Cargo ) if not Cargo:IsLoaded() then self:__Board( 10, Cargo ) else - self:__Loaded( 1, 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 + if Cargo:IsUnLoaded() then + if Cargo:IsInLoadRadius( HelicopterUnit:GetCoordinate() ) then + local CargoBayFreeWeight = HelicopterUnit:GetCargoBayFreeWeight() + local CargoWeight = Cargo:GetWeight() + + self:F({CargoBayFreeWeight=CargoBayFreeWeight}) + + -- Only when there is space within the bay to load the next cargo item! + if CargoBayFreeWeight > CargoWeight then --and CargoBayFreeVolume > CargoVolume then + Cargo:Board( HelicopterUnit, 25 ) + self:__Board( 10, Cargo ) + self.Helicopter_Cargo[HelicopterUnit] = Cargo + return + end + end + end + end + end + self:__Loaded( 1, Cargo ) -- Will only be executed when no more cargo is boarded. end end end ---- On before Land event. + +--- On before Loaded event. +-- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @return #boolean Cargo loaded. +function AI_CARGO_HELICOPTER:onbeforeLoaded( Helicopter, From, Event, To, Cargo ) + self:F( { Helicopter, From, Event, To } ) + + local Loaded = true + + if Helicopter and Helicopter:IsAlive() then + for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Helicopter:GetName() } ) + if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then + Loaded = false + end + end + end + + return Loaded + +end + + + + +--- On after Loaded event. Check if cargo is loaded. -- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter -- @param #string From From state. @@ -430,24 +504,12 @@ end -- @param #string To To state. -- @param Cargo.Cargo#CARGO Cargo Cargo object. -- @return #boolean Cargo is loaded. -function AI_CARGO_HELICOPTER:onbeforeLoaded( Helicopter, From, Event, To, Cargo ) +function AI_CARGO_HELICOPTER:onafterLoaded( Helicopter, From, Event, To, Cargo ) self:F( { Helicopter, From, Event, To, Cargo } ) - local Loaded = true - if Helicopter and Helicopter:IsAlive() then - for HelicopterUnit, Cargo in pairs( self.Helicopter_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 - + self.Transporting = true end - - return Loaded - end @@ -463,9 +525,11 @@ function AI_CARGO_HELICOPTER:onafterUnload( Helicopter, From, Event, To, Deploye for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT for _, Cargo in pairs( HelicopterUnit:GetCargo() ) do - Cargo:UnBoard() - Cargo:SetDeployed( true ) - self:__Unboard( 10, Cargo, Deployed ) + if Cargo:IsLoaded() then + Cargo:UnBoard() + Cargo:SetDeployed( true ) + self:__Unboard( 10, Cargo, Deployed ) + end end end end @@ -487,6 +551,17 @@ function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To, Cargo, if not Cargo:IsUnLoaded() then self:__Unboard( 10, Cargo, Deployed ) else + for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do + local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT + for _, Cargo in pairs( HelicopterUnit:GetCargo() ) do + if Cargo:IsLoaded() then + Cargo:UnBoard() + Cargo:SetDeployed( true ) + self:__Unboard( 10, Cargo, Deployed ) + return + end + end + end self:__Unloaded( 1, Cargo, Deployed ) end end @@ -511,21 +586,16 @@ function AI_CARGO_HELICOPTER:onbeforeUnloaded( Helicopter, From, Event, To, Carg if Helicopter and Helicopter:IsAlive() then for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do - local CargoCheck = self.Helicopter_Cargo[HelicopterUnit] -- Cargo.Cargo#CARGO - if CargoCheck then - self:F( { CargoCheck:GetName(), IsUnLoaded = CargoCheck:IsUnLoaded() } ) - if CargoCheck:IsUnLoaded() == false then + local IsEmpty = HelicopterUnit:IsCargoEmpty() + self:I({ IsEmpty = IsEmpty }) + if not IsEmpty then AllUnloaded = false break - end end end if AllUnloaded == true then if Deployed == true then - for HelicopterUnit, Cargo in pairs( self.Helicopter_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - end self.Helicopter_Cargo = {} end self.Helicopter = Helicopter @@ -632,11 +702,11 @@ end --- On after Deploy event. -- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter +-- @param Wrapper.Group#GROUP Helicopter Transport helicopter. -- @param From -- @param Event -- @param To --- @param Core.Point#COORDINATE Coordinate +-- @param Core.Point#COORDINATE Coordinate Place at which the cargo is deployed. -- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed ) diff --git a/Moose Development/Moose/Cargo/Cargo.lua b/Moose Development/Moose/Cargo/Cargo.lua index 42d309c6a..82015511e 100644 --- a/Moose Development/Moose/Cargo/Cargo.lua +++ b/Moose Development/Moose/Cargo/Cargo.lua @@ -779,7 +779,8 @@ do -- CARGO local Distance = 0 if self:IsUnLoaded() then - Distance = Coordinate:Get2DDistance( self.CargoObject:GetCoordinate() ) + local CargoCoordinate = self.CargoObject:GetCoordinate() + Distance = Coordinate:Get2DDistance( CargoCoordinate ) self:T( Distance ) if Distance <= self.LoadRadius then return true @@ -810,7 +811,7 @@ do -- CARGO end - --- Check if CargoCarrier is near the Cargo to be Loaded. + --- Check if CargoCarrier is near the coordinate within NearRadius. -- @param #CARGO self -- @param Core.Point#COORDINATE Coordinate -- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). @@ -875,6 +876,13 @@ do -- CARGO return self.CargoObject:GetCoordinate() end + --- Get the weight of the cargo. + -- @param #CARGO self + -- @return #number Weight The weight in kg. + function CARGO:GetWeight() + return self.Weight + end + --- Set the weight of the cargo. -- @param #CARGO self -- @param #number Weight The weight in kg. @@ -884,6 +892,22 @@ do -- CARGO return self end + --- Get the volume of the cargo. + -- @param #CARGO self + -- @return #number Volume The volume in kg. + function CARGO:GetVolume() + return self.Volume + end + + --- Set the volume of the cargo. + -- @param #CARGO self + -- @param #number Volume The volume in kg. + -- @return #CARGO + function CARGO:SetVolume( Volume ) + self.Volume = Volume + return self + end + --- Send a CC message to a @{Wrapper.Group}. -- @param #CARGO self -- @param #string Message @@ -997,13 +1021,27 @@ do -- CARGO_REPRESENTABLE -- @param #CARGO_REPRESENTABLE self -- @param #string Type -- @param #string Name - -- @param #number Weight -- @param #number LoadRadius (optional) -- @param #number NearRadius (optional) -- @return #CARGO_REPRESENTABLE - function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, Weight, LoadRadius, NearRadius ) - local self = BASE:Inherit( self, CARGO:New( Type, Name, Weight, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE - self:F( { Type, Name, Weight, LoadRadius, NearRadius } ) + function CARGO_REPRESENTABLE:New( CargoObject, Type, Name, LoadRadius, NearRadius ) + local self = BASE:Inherit( self, CARGO:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_REPRESENTABLE + self:F( { Type, Name, LoadRadius, NearRadius } ) + + local Desc = CargoObject:GetDesc() + self:I( { Desc = Desc } ) + local Weight = math.random( 80, 120 ) + if Desc then + Weight = Desc.massEmpty + end + + self:SetWeight( Weight ) + +-- local Box = CargoUnit:GetBoundingBox() +-- local VolumeUnit = ( Box.max.x - Box.min.x ) * ( Box.max.y - Box.min.y ) * ( Box.max.z - Box.min.z ) +-- self:I( { VolumeUnit = VolumeUnit, WeightUnit = WeightUnit } ) + --self:SetVolume( VolumeUnit ) + return self end @@ -1203,6 +1241,10 @@ end -- @param #string From -- @param #string To -- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed +-- @param #number BoardDistance +-- @param #number LoadDistance +-- @param #number Angle function CARGO_PACKAGE:onafterOnBoarded( From, Event, To, CargoCarrier, Speed, BoardDistance, LoadDistance, Angle ) self:F() @@ -1218,6 +1260,7 @@ end -- @param #string Event -- @param #string From -- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier -- @param #number Speed -- @param #number UnLoadDistance -- @param #number UnBoardDistance @@ -1261,6 +1304,7 @@ end -- @param #string From -- @param #string To -- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed function CARGO_PACKAGE:onafterUnBoarded( From, Event, To, CargoCarrier, Speed ) self:F() @@ -1304,6 +1348,8 @@ end -- @param #string Event -- @param #string From -- @param #string To +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param #number Speed -- @param #number Distance -- @param #number Angle function CARGO_PACKAGE:onafterUnLoad( From, Event, To, CargoCarrier, Speed, Distance, Angle ) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index 459da2a0f..63f9203d1 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -61,8 +61,8 @@ do -- CARGO_GROUP -- @param #number LoadRadius (optional) Distance in meters until which a cargo is loaded into the carrier. Cargo outside this radius has to be routed by other means to within the radius to be loaded. -- @param #number NearRadius (optional) Once the units are within this radius of the carrier, they are actually loaded, i.e. disappear from the scene. -- @return #CARGO_GROUP Cargo group object. - function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius ) - local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius ) ) -- #CARGO_GROUP + function CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) + local self = BASE:Inherit( self, CARGO_REPORTABLE:New( Type, Name, 0, LoadRadius, NearRadius ) ) -- #CARGO_GROUP self:F( { Type, Name, LoadRadius } ) self.CargoSet = SET_CARGO:New() @@ -73,6 +73,7 @@ do -- CARGO_GROUP self:SetDeployed( false ) local WeightGroup = 0 + local VolumeGroup = 0 self.CargoGroup:Destroy() @@ -88,7 +89,7 @@ do -- CARGO_GROUP for UnitID, UnitTemplate in pairs( self.CargoTemplate.units ) do UnitTemplate.name = UnitTemplate.name .. "#CARGO" - local CargoUnitName = UnitTemplate.name + local CargoUnitName = UnitTemplate.name self.CargoUnitTemplate[CargoUnitName] = UnitTemplate GroupTemplate.units[#GroupTemplate.units+1] = self.CargoUnitTemplate[CargoUnitName] @@ -96,20 +97,29 @@ do -- CARGO_GROUP -- And we register the spawned unit as part of the CargoSet. local Unit = UNIT:Register( CargoUnitName ) - --local WeightUnit = Unit:GetDesc().massEmpty - --WeightGroup = WeightGroup + WeightUnit - 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) + self.CargoGroup = GROUP:NewTemplate( GroupTemplate, GroupTemplate.CoalitionID, GroupTemplate.CategoryID, GroupTemplate.CountryID ) -- Now we spawn the new group based on the template created. self.CargoObject = _DATABASE:Spawn( GroupTemplate ) + + for CargoUnitID, CargoUnit in pairs( self.CargoObject:GetUnits() ) do + + + local CargoUnitName = CargoUnit:GetName() + + local Cargo = CARGO_UNIT:New( CargoUnit, Type, CargoUnitName, LoadRadius, NearRadius ) + self.CargoSet:Add( CargoUnitName, Cargo ) + + WeightGroup = WeightGroup + Cargo:GetWeight() + --VolumeGroup = VolumeGroup + VolumeUnit + + end self:SetWeight( WeightGroup ) - self.CargoLimit = 10 self:T( { "Weight Cargo", WeightGroup } ) @@ -257,10 +267,11 @@ do -- CARGO_GROUP --- Enter Boarding State. -- @param #CARGO_GROUP self - -- @param Wrapper.Unit#UNIT CargoCarrier -- @param #string Event -- @param #string From -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) --self:F( { CargoCarrier.UnitName, From, Event, To } ) @@ -303,11 +314,12 @@ do -- CARGO_GROUP end --- Leave Boarding State. - -- @param #CARGO_GROUP self - -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #CARGO_GROUP self -- @param #string Event -- @param #string From -- @param #string To + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) --self:F( { CargoCarrier.UnitName, From, Event, To } ) @@ -359,10 +371,11 @@ do -- CARGO_GROUP --- Enter UnBoarding State. -- @param #CARGO_GROUP self - -- @param Core.Point#POINT_VEC2 ToPointVec2 -- @param #string Event -- @param #string From -- @param #string To + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) self:F( {From, Event, To, ToPointVec2, NearRadius } ) @@ -401,10 +414,11 @@ do -- CARGO_GROUP --- Leave UnBoarding State. -- @param #CARGO_GROUP self - -- @param Core.Point#POINT_VEC2 ToPointVec2 -- @param #string Event -- @param #string From -- @param #string To + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) --self:F( { From, Event, To, ToPointVec2, NearRadius } ) @@ -438,10 +452,11 @@ do -- CARGO_GROUP --- UnBoard Event. -- @param #CARGO_GROUP self - -- @param Core.Point#POINT_VEC2 ToPointVec2 -- @param #string Event -- @param #string From -- @param #string To + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #number NearRadius If distance is smaller than this number, cargo is loaded into the carrier. function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) --self:F( { From, Event, To, ToPointVec2, NearRadius } ) @@ -454,10 +469,10 @@ do -- CARGO_GROUP --- Enter UnLoaded State. -- @param #CARGO_GROUP self - -- @param Core.Point#POINT_VEC2 -- @param #string Event -- @param #string From -- @param #string To + -- @param Core.Point#POINT_VEC2 function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) --self:F( { From, Event, To, ToPointVec2 } ) @@ -467,7 +482,7 @@ do -- CARGO_GROUP self.CargoSet:ForEach( function( Cargo ) --Cargo:UnLoad( ToPointVec2 ) - local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10) + local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(20, 10) Cargo:UnLoad( RandomVec2 ) end ) @@ -485,8 +500,6 @@ do -- CARGO_GROUP -- @return Core.Point#COORDINATE The current Coordinate of the first Cargo of the CargoGroup. -- @return #nil There is no valid Cargo in the CargoGroup. function CARGO_GROUP:GetCoordinate() - self:F() - local Cargo = self:GetFirstAlive() -- Cargo.Cargo#CARGO if Cargo then @@ -592,8 +605,7 @@ do -- CARGO_GROUP -- @param #CARGO_GROUP self -- @param Wrapper.Group#GROUP CargoCarrier -- @param #number NearRadius - -- @return #boolean The Cargo is near to the Carrier. - -- @return #nil The Cargo is not near to the Carrier. + -- @return #boolean The Cargo is near to the Carrier or #nil if the Cargo is not near to the Carrier. function CARGO_GROUP:IsNear( CargoCarrier, NearRadius ) self:F( {NearRadius = NearRadius } ) @@ -621,12 +633,19 @@ do -- CARGO_GROUP if Cargo then local Distance = 0 + local CargoCoordinate if Cargo:IsLoaded() then - Distance = Coordinate:Get2DDistance( Cargo.CargoCarrier:GetCoordinate() ) + CargoCoordinate = Cargo.CargoCarrier:GetCoordinate() else - Distance = Coordinate:Get2DDistance( Cargo.CargoObject:GetCoordinate() ) + CargoCoordinate = Cargo.CargoObject:GetCoordinate() end +-- if CargoCoordinate then + Distance = Coordinate:Get2DDistance( CargoCoordinate ) +-- else +-- return false +-- end + self:F( { Distance = Distance, LoadRadius = self.LoadRadius } ) if Distance <= self.LoadRadius then return true diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index 2a5f009ac..da9c73f2b 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -42,9 +42,9 @@ do -- CARGO_UNIT -- @param #number LoadRadius (optional) -- @param #number NearRadius (optional) -- @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:I( { Type, Name, Weight, NearRadius } ) + function CARGO_UNIT:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) + local self = BASE:Inherit( self, CARGO_REPRESENTABLE:New( CargoUnit, Type, Name, LoadRadius, NearRadius ) ) -- #CARGO_UNIT + self:I( { Type, Name, LoadRadius, NearRadius } ) self:T( CargoUnit ) self.CargoObject = CargoUnit @@ -62,6 +62,7 @@ do -- CARGO_UNIT -- @param #string From -- @param #string To -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #number NearRadius (optional) Defaut 25 m. function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:F( { From, Event, To, ToPointVec2, NearRadius } ) @@ -131,6 +132,7 @@ do -- CARGO_UNIT -- @param #string From -- @param #string To -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #number NearRadius (optional) Defaut 100 m. function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:F( { From, Event, To, ToPointVec2, NearRadius } ) @@ -158,6 +160,7 @@ do -- CARGO_UNIT -- @param #string From -- @param #string To -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #number NearRadius (optional) Defaut 100 m. function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:F( { From, Event, To, ToPointVec2, NearRadius } ) @@ -223,8 +226,6 @@ do -- CARGO_UNIT function CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, NearRadius, ... ) self:F( { From, Event, To, CargoCarrier, NearRadius } ) - local NearRadius = NearRadius or 25 - self.CargoInAir = self.CargoObject:InAir() local Desc = self.CargoObject:GetDesc() @@ -236,6 +237,9 @@ do -- CARGO_UNIT -- Only move the group to the carrier when the cargo is not in the air -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea). if not self.CargoInAir then + -- If NearRadius is given, then use the given NearRadius, otherwise calculate the NearRadius + -- based upon the Carrier bounding radius, which is calculated from the bounding rectangle on the Y axis. + local NearRadius = CargoCarrier:GetBoundingRadius( NearRadius ) if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then self:Load( CargoCarrier, NearRadius, ... ) else @@ -247,8 +251,6 @@ do -- CARGO_UNIT local Angle = 180 local Distance = 5 - NearRadius = NearRadius or 25 - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) @@ -281,7 +283,7 @@ do -- CARGO_UNIT -- @param #string From -- @param #string To -- @param Wrapper.Client#CLIENT CargoCarrier - -- @param #number NearRadius + -- @param #number NearRadius Default 25 m. function CARGO_UNIT:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) --self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) @@ -299,8 +301,6 @@ do -- CARGO_UNIT local Angle = 180 local Distance = 5 - NearRadius = NearRadius or 25 - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() local CargoCarrierHeading = CargoCarrier:GetHeading() -- Get Heading of object in degrees. local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) @@ -338,6 +338,7 @@ do -- CARGO_UNIT -- @param #string From -- @param #string To -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #number NearRadius Default 25 m. function CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) --self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) @@ -345,8 +346,6 @@ do -- CARGO_UNIT local Angle = 180 local Distance = 5 - local NearRadius = NearRadius or 25 - if From == "UnLoaded" or From == "Boarding" then end diff --git a/Moose Development/Moose/Core/Base.lua b/Moose Development/Moose/Core/Base.lua index 83d9cd5a7..1b4d96efb 100644 --- a/Moose Development/Moose/Core/Base.lua +++ b/Moose Development/Moose/Core/Base.lua @@ -798,8 +798,7 @@ end -- @param Object The object that will hold the Value set by the Key. -- @param Key The key that is used as a reference of the value. Note that the key can be a #string, but it can also be any other type! -- @param Value The value to is stored in the object. --- @return The Value set. --- @return #nil The Key was not found and thus the Value could not be retrieved. +-- @return The Value set. function BASE:SetState( Object, Key, Value ) local ClassNameAndID = Object:GetClassNameAndID() @@ -816,7 +815,7 @@ end -- @param #BASE self -- @param Object The object that holds the Value set by the Key. -- @param Key The key that is used to retrieve the value. Note that the key can be a #string, but it can also be any other type! --- @return The Value retrieved. +-- @return The Value retrieved or nil if the Key was not found and thus the Value could not be retrieved. function BASE:GetState( Object, Key ) local ClassNameAndID = Object:GetClassNameAndID() @@ -829,6 +828,10 @@ function BASE:GetState( Object, Key ) return nil end +--- Clear the state of an object. +-- @param #BASE self +-- @param Object The object that holds the Value set by the Key. +-- @param StateName The key that is should be cleared. function BASE:ClearState( Object, StateName ) local ClassNameAndID = Object:GetClassNameAndID() diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 3b0fb0337..59e4676e4 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -374,14 +374,15 @@ do -- cargo -- @return #DATABASE self function DATABASE:_RegisterCargos() + local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug! - for CargoGroupName, CargoGroup in pairs( self.GROUPS ) do + for CargoGroupName, CargoGroup in pairs( Groups ) do + self:I( { Cargo = CargoGroupName } ) if self:IsCargo( CargoGroupName ) then local CargoInfo = CargoGroupName:match("~CARGO(.*)") local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") local CargoName1 = CargoGroupName:match("(.*)~CARGO%(.*%)") local CargoName2 = CargoGroupName:match(".*~CARGO%(.*%)(.*)") - self:E({CargoName1 = CargoName1, CargoName2 = CargoName2 }) local CargoName = CargoName1 .. ( CargoName2 or "" ) local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName @@ -459,7 +460,7 @@ end function DATABASE:AddGroup( GroupName ) if not self.GROUPS[GroupName] then - self:E( { "Add GROUP:", GroupName } ) + self:I( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) end @@ -471,7 +472,7 @@ end function DATABASE:AddPlayer( UnitName, PlayerName ) if PlayerName then - self:E( { "Add player for unit:", UnitName, PlayerName } ) + self:I( { "Add player for unit:", UnitName, PlayerName } ) self.PLAYERS[PlayerName] = UnitName self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName ) self.PLAYERSJOINED[PlayerName] = PlayerName @@ -483,7 +484,7 @@ end function DATABASE:DeletePlayer( UnitName, PlayerName ) if PlayerName then - self:E( { "Clean player:", PlayerName } ) + self:I( { "Clean player:", PlayerName } ) self.PLAYERS[PlayerName] = nil self.PLAYERUNITS[PlayerName] = nil end @@ -750,7 +751,7 @@ function DATABASE:_RegisterPlayers() local UnitName = UnitData:getName() local PlayerName = UnitData:getPlayerName() if not self.PLAYERS[PlayerName] then - self:E( { "Add player for unit:", UnitName, PlayerName } ) + self:I( { "Add player for unit:", UnitName, PlayerName } ) self:AddPlayer( UnitName, PlayerName ) end end @@ -773,13 +774,13 @@ function DATABASE:_RegisterGroupsAndUnits() if DCSGroup:isExist() then local DCSGroupName = DCSGroup:getName() - self:E( { "Register Group:", DCSGroupName } ) + self:I( { "Register Group:", DCSGroupName } ) self:AddGroup( DCSGroupName ) for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do local DCSUnitName = DCSUnit:getName() - self:E( { "Register Unit:", DCSUnitName } ) + self:I( { "Register Unit:", DCSUnitName } ) self:AddUnit( DCSUnitName ) end else @@ -788,6 +789,11 @@ function DATABASE:_RegisterGroupsAndUnits() end end + + self:I("Groups:") + for GroupName, Group in pairs( self.GROUPS ) do + self:I( { "Group:", GroupName } ) + end return self end @@ -798,7 +804,7 @@ end function DATABASE:_RegisterClients() for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do - self:E( { "Register Client:", ClientName } ) + self:I( { "Register Client:", ClientName } ) self:AddClient( ClientName ) end @@ -809,14 +815,14 @@ end function DATABASE:_RegisterStatics() local CoalitionsData = { GroupsRed = coalition.getStaticObjects( coalition.side.RED ), GroupsBlue = coalition.getStaticObjects( coalition.side.BLUE ) } - self:E( { Statics = CoalitionsData } ) + self:I( { Statics = CoalitionsData } ) for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSStaticId, DCSStatic in pairs( CoalitionData ) do if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() - self:E( { "Register Static:", DCSStaticName } ) + self:I( { "Register Static:", DCSStaticName } ) self:AddStatic( DCSStaticName ) else self:E( { "Static does not exist: ", DCSStatic } ) @@ -836,7 +842,7 @@ function DATABASE:_RegisterAirbases() local DCSAirbaseName = DCSAirbase:getName() - self:E( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } ) + self:I( { "Register Airbase:", DCSAirbaseName, DCSAirbase:getID() } ) self:AddAirbase( DCSAirbaseName ) end end @@ -866,9 +872,8 @@ function DATABASE:_EventOnBirth( Event ) Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) local PlayerName = Event.IniUnit:GetPlayerName() - self:E( { "PlayerName:", PlayerName } ) if PlayerName then - self:E( { "Player Joined:", PlayerName } ) + self:I( { "Player Joined:", PlayerName } ) if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) end @@ -937,7 +942,7 @@ function DATABASE:_EventOnPlayerLeaveUnit( Event ) if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() if PlayerName and self.PLAYERS[PlayerName] then - self:E( { "Player Left:", PlayerName } ) + self:I( { "Player Left:", PlayerName } ) local Settings = SETTINGS:Set( PlayerName ) Settings:RemovePlayerMenu( Event.IniUnit ) self:DeletePlayer( Event.IniUnit, PlayerName ) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index a1be00587..c7f937c8b 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -719,7 +719,7 @@ do -- Event Creation -- @param #EVENT self -- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created. function EVENT:CreateEventNewCargo( Cargo ) - self:F( { Cargo } ) + self:I( { Cargo } ) local Event = { id = EVENTS.NewCargo, diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 1333a43da..5ccfc3b65 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -308,7 +308,7 @@ end --- Sends a MESSAGE to all players. -- @param #MESSAGE self --- @param Core.Settings#Settings (Optional) Settings for message display +-- @param Core.Settings#Settings Settings (Optional) Settings for message display. -- @return #MESSAGE -- @usage -- -- Send a message created to all players. diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index e3c478b14..8c456137d 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1095,6 +1095,37 @@ do -- COORDINATE return RoutePoint end + + --- Gets the nearest airbase with respect to the current coordinates. + -- @param #COORDINATE self + -- @param #number AirbaseCategory Category of the airbase. + -- @return Wrapper.Airbase#AIRBASE Closest Airbase to the given coordinate. + -- @return #number Distance to the closest airbase in meters. + function COORDINATE:GetClosestAirbase(AirbaseCategory) + local airbases=AIRBASE.GetAllAirbases() + + local closest=nil + local distmin=nil + -- Loop over all airbases. + for _,_airbase in pairs(airbases) do + local airbase=_airbase --Wrapper.Airbase#AIRBASE + local category=airbase:GetDesc().category + if AirbaseCategory and AirbaseCategory==category or AirbaseCategory==nil then + local dist=self:Get2DDistance(airbase:GetCoordinate()) + if closest==nil then + distmin=dist + closest=airbase + else + if dist0 then + table.insert(departures, airport) -- insert airport object. + end end end @@ -3034,6 +3092,14 @@ function RAT:_PickDeparture(takeoff) dep=AIRBASE:FindByName(name):GetZone() else dep=AIRBASE:FindByName(name) + -- Check if the airport has a valid parking spot + if self.termtype~=nil and dep~=nil then + local _dep=dep --Wrapper.Airbase#AIRBASE + local nspots=_dep:GetParkingSpotsNumber(self.termtype) + if nspots==0 then + dep=nil + end + end end elseif self:_ZoneExists(name) then if takeoff==RAT.wp.air then @@ -3098,7 +3164,8 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) if random then -- Airports of friendly coalitions. - for _,airport in pairs(self.airports) do + for _,_airport in pairs(self.airports) do + local airport=_airport --Wrapper.Airbase#AIRBASE local name=airport:GetName() if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then @@ -3110,7 +3177,14 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) if landing==RAT.wp.air then table.insert(destinations, airport:GetZone()) -- insert zone object. else - table.insert(destinations, airport) -- insert airport object. + -- Check if the requested terminal type is available. + local nspot=1 + if self.termtype then + nspot=airport:GetParkingSpotsNumber(self.termtype) + end + if nspot>0 then + table.insert(destinations, airport) -- insert airport object. + end end end end @@ -3130,6 +3204,14 @@ function RAT:_PickDestination(departure, q, minrange, maxrange, random, landing) dest=AIRBASE:FindByName(name):GetZone() else dest=AIRBASE:FindByName(name) + -- Check if the requested terminal type is available. + local nspot=1 + if self.termtype then + nspot=dest:GetParkingSpotsNumber(self.termtype) + end + if nspot==0 then + dest=nil + end end elseif self:_ZoneExists(name) then if landing==RAT.wp.air then @@ -3287,16 +3369,31 @@ function RAT:_GetAirportsOfMap() end end ---- Get all "friendly" airports of the current map. +--- Get all "friendly" airports of the current map. Fills the self.airports{} table. -- @param #RAT self function RAT:_GetAirportsOfCoalition() for _,coalition in pairs(self.ctable) do - for _,airport in pairs(self.airports_map) do + for _,_airport in pairs(self.airports_map) do + local airport=_airport --Wrapper.Airbase#AIRBASE + local category=airport:GetDesc().category if airport:GetCoalition()==coalition then -- Planes cannot land on FARPs. - local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP" + --local condition1=self.category==RAT.cat.plane and airport:GetTypeName()=="FARP" + local condition1=self.category==RAT.cat.plane and category==Airbase.Category.HELIPAD -- Planes cannot land on ships. - local condition2=self.category==RAT.cat.plane and airport:GetCategory()==1 + --local condition2=self.category==RAT.cat.plane and airport:GetCategory()==1 + local condition2=self.category==RAT.cat.plane and category==Airbase.Category.SHIP + + -- Check that airport has the requested terminal types. + -- NOT good here because we would also not allow any airport zones! + --[[ + local nspots=1 + if self.termtype then + nspots=airport:GetParkingSpotsNumber(self.termtype) + end + local condition3 = nspots==0 + ]] + if not (condition1 or condition2) then table.insert(self.airports, airport) end @@ -3305,8 +3402,8 @@ function RAT:_GetAirportsOfCoalition() end if #self.airports==0 then - local text="ERROR! No possible departure/destination airports found." - MESSAGE:New(text, 30):ToAll() + local text=string.format("No possible departure/destination airports found for RAT %s.", tostring(self.alias)) + MESSAGE:New(text, 10):ToAll() self:E(RAT.id..text) end end @@ -5254,7 +5351,7 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take if spawnonground then - -- Shíps and FARPS seem to have a build in queue. + -- Sh�ps and FARPS seem to have a build in queue. if spawnonship or spawnonfarp or spawnonrunway or automatic then self:T(RAT.id..string.format("RAT group %s spawning at farp, ship or runway %s.", self.alias, departure:GetName())) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 1e39d0735..e7c015056 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -68,6 +68,8 @@ -- @field #number dtBombtrack Time step [sec] used for tracking released bomb/rocket positions. Default 0.005 seconds. -- @field #number BombtrackThreshold Bombs/rockets/missiles are only tracked if player-range distance is smaller than this threashold [m]. Default 25000 m. -- @field #number Tmsg Time [sec] messages to players are displayed. Default 30 sec. +-- @field #string examinergroupname Name of the examiner group which should get all messages. +-- @field #boolean examinerexclusive If true, only the examiner gets messages. If false, clients and examiner get messages. -- @field #number strafemaxalt Maximum altitude above ground for registering for a strafe run. Default is 914 m = 3000 ft. -- @field #number ndisplayresult Number of (player) results that a displayed. Default is 10. -- @field Utilities.Utils#SMOKECOLOR BombSmokeColor Color id used for smoking bomb targets. @@ -234,6 +236,8 @@ RANGE={ dtBombtrack=0.005, BombtrackThreshold=25000, Tmsg=30, + examinergroupname=nil, + examinerexclusive=nil, strafemaxalt=914, ndisplayresult=10, BombSmokeColor=SMOKECOLOR.Red, @@ -279,8 +283,8 @@ RANGE.MenuF10={} RANGE.id="RANGE | " --- Range script version. --- @field #number version -RANGE.version="1.2.0" +-- @field #string version +RANGE.version="1.2.1" --TODO list: --TODO: Add custom weapons, which can be specified by the user. @@ -434,6 +438,15 @@ function RANGE:SetMessageTimeDuration(time) self.Tmsg=time or RANGE.Defaults.Tmsg end +--- Set messages to examiner. The examiner will receive messages from all clients. +-- @param #RANGE self +-- @param #string examinergroupname Name of the group of the examiner. +-- @param #boolean exclusively If true, messages are send exclusively to the examiner, i.e. not to the clients. +function RANGE:SetMessageToExaminer(examinergroupname, exclusively) + self.examinergroupname=examinergroupname + self.examinerexclusive=exclusively +end + --- Set max number of player results that are displayed. -- @param #RANGE self -- @param #number nmax Number of results. Default is 10. @@ -2119,13 +2132,24 @@ function RANGE:_DisplayMessageToGroup(_unit, _text, _time, _clear) -- Group ID. local _gid=_unit:GetGroup():GetID() - if _gid then + if _gid and not self.examinerexclusive then if _clear == true then trigger.action.outTextForGroup(_gid, _text, _time, _clear) else trigger.action.outTextForGroup(_gid, _text, _time) end end + + if self.examinergroupname~=nil then + local _examinerid=GROUP:FindByName(self.examinergroupname):GetID() + if _examinerid then + if _clear == true then + trigger.action.outTextForGroup(_examinerid, _text, _time, _clear) + else + trigger.action.outTextForGroup(_examinerid, _text, _time) + end + end + end end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua new file mode 100644 index 000000000..cf134349b --- /dev/null +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -0,0 +1,1129 @@ +--- **Functional** - (R2.4) - Manages assets of an airbase and transportation to other airbases upon request. +-- +-- +-- Features: +-- +-- * Holds (virtual) assests such as intrantry groups in stock. +-- * Manages requests of assets from other airbases or warehouses. +-- * Take care of transportation to other airbases. +-- * Different means of automatic transportation (planes, helicopters, selfpropelled). +-- +-- # QUICK START GUIDE +-- +-- === +-- +-- ### Authors: **funkyfranky** +-- +-- @module Functional.Warehouse +-- @image Warehouse.JPG + +--- WAREHOUSE class. +-- @type WAREHOUSE +-- @field #string ClassName Name of the class. +-- @field #boolean Debug If true, send debug messages to all. +-- @field #boolean Report If true, send status messages to coalition. +-- @field DCS#Coalition coalition Coalition the warehouse belongs to. +-- @field Wrapper.Airbase#AIRBASE homebase Airbase the warehouse belongs to. +-- @field DCS#Airbase.Category category Category of the home airbase, i.e. airdrome, helipad/farp or ship. +-- @field Core.Point#COORDINATE coordinate Coordinate of the warehouse. +-- @field Core.Zone#ZONE spawnzone Zone in which assets are spawned. +-- @field #string wid Identifier of the warehouse printed before other output to DCS.log file. +-- @field #number markerid ID of the warehouse marker at the airbase. +-- @field #number assetid Unique id of asset items in stock. Essentially a running number starting at one and incremented when a new asset is added. +-- @field #table stock Table holding all assets in stock. Table entries are of type @{#WAREHOUSE.Stockitem}. +-- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. +-- @extends Core.Fsm#FSM + +--- Manages ground assets of an airbase and offers the possibility to transport them to another airbase or warehouse. +-- +-- === +-- +-- # Demo Missions +-- +-- ### None. +-- +-- === +-- +-- # YouTube Channel +-- +-- ### None. +-- +-- === +-- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.JPG) +-- +-- # What is a warehouse? +-- A warehouse is an abstract object that can hold virtual assets in stock. It is usually associated with a particular airbase. +-- If another airbase or warehouse requests assets, the corresponding troops are spawned at the warehouse and being transported to the requestor. +-- +-- ## What assets can be stored? +-- Any kind of ground or airborn asset can be stored. Ships not supported at the moment due to the fact that airbases are bound to airbases which are +-- normally not located near the sea. +-- +-- +-- +-- === +-- +-- # USAGE GUIDE +-- +-- +-- +-- @field #WAREHOUSE +WAREHOUSE = { + ClassName = "WAREHOUSE", + Debug = false, + Report = true, + coalition = nil, + homebase = nil, + category = nil, + coordinate = nil, + spawnzone = nil, + wid = nil, + markerid = nil, + assetid = 0, + queueid = 0, + stock = {}, + queue = {}, +} + +--- Item of the warehouse stock table. +-- @type WAREHOUSE.Stockitem +-- @field #number id Unique id of the asset. +-- @field #string templatename Name of the template group. +-- @field DCS#Group.Category category Category of the group. +-- @field #string unittype Type of the first unit of the group as obtained by the Object.getTypeName() DCS API function. +-- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. + +--- Item of the warehouse queue table. +-- queueitem={uid=self.qid, prio=Prio, airbase=Airbase, assetdesc=AssetDescriptor, assetdescval=AssetDescriptorValue, nasset=nAsset, transporttype=TransportType, ntransport=nTransport} +-- @type WAREHOUSE.Queueitem +-- @field #number uid Unique id of the queue item. +-- @field #number prio Priority of the request. +-- @field Wrapper.Airbase#AIRBASE airbase Requesting airbase. +-- @field DCS#Airbase.Category category Category of the requesting airbase, i.e. airdrome, helipad/farp or ship. +-- @field #WAREHOUSE.Descriptor assetdesc Descriptor of the requested asset. +-- @field assetdescval Value of the asset descriptor. Type depends on descriptor. +-- @field #number nasset Number of asset groups requested. +-- @field #WAREHOUSE.TransportType transporttype Transport unit type. +-- @field #number ntransport Number of transport units requested. + +--- Descriptors enumerator describing the type of the asset in stock. +-- @type WAREHOUSE.Descriptor +WAREHOUSE.Descriptor = { + ID="id", + TEMPLATENAME="templatename", + CATEGORY="category", + UNITTYPE="unittype", + ATTRIBUTE="attribute", +} + +--- Warehouse unit categories. These are used for +-- @type WAREHOUSE.Attribute +WAREHOUSE.Attribute = { + TRANSPORT_PLANE="Transport_Plane", + TRANSPORT_HELO="Transport_Helo", + TRANSPORT_APC="Transport_APC", + FIGHTER="Fighter", + TANKER="Tanker", + AWACS="AWACS", + ARTILLERY="Artillery", + ATTACKHELICOPTER="Attackhelicopter", + INFANTRY="Infantry", + BOMBER="Bomber", + TANK="Tank", + TRUCK="Truck", + SHIP="Ship", + OTHER="Other", +} + +--- Cargo transport type. +-- @type WAREHOUSE.TransportType +WAREHOUSE.TransportType = { + AIRPLANE = "Transport_Plane", + HELICOPTER = "Transport_Helo", + APC = "Transport_APC", + SHIP = "Ship", + TRAIN = "Train", + SELFPROPELLED = "Selfporpelled", +} + +--- Warehouse class version. +-- @field #string version +WAREHOUSE.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Warehuse todo list. +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: Add event handlers. +-- TODO: Add AI_APC +-- TODO: Add AI_HELICOPTER +-- TODO: Write documentation. +-- TODO: Put active groups into the warehouse. +-- TODO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. +-- TODO: Handle cases with immobile units. +-- TODO: Add queue. +-- TODO: How to handle multiple units in a transport group? +-- TODO: Switch to AI_XXX_DISPATCHER + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor(s) +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- WAREHOUSE constructor. Creates a new WAREHOUSE object accociated with an airbase. +-- @param #WAREHOUSE self +-- @param Wrapper.Airbase#AIRBASE airbase The airbase at which the warehouse is constructed. +-- @return #WAREHOUSE self +function WAREHOUSE:NewAirbase(airbase) + BASE:E({airbase=airbase}) + + -- Print version. + env.info(string.format("Adding warehouse v%s for airbase %s", WAREHOUSE.version, airbase:GetName())) + + -- Inherit everthing from FSM class. + local self = BASE:Inherit( self, FSM:New() ) -- #WAREHOUSE + + -- Set some string id for output to DCS.log file. + self.wid=string.format("WAREHOUSE %s | ", airbase:GetName()) + + -- Set some variables. + self.homebase=airbase + self.coordinate=airbase:GetCoordinate() + self.coalition=airbase:GetCoalition() + self.category=airbase:GetDesc().category + + -- Get the closest point on road. + local _road=self.coordinate:GetClosestPointToRoad():GetVec2() + + -- Define the default spawn zone. + self.spawnzone=ZONE_RADIUS:New("Spawnzone",_road, 200) + self.spawnzone:BoundZone(60,country.id.GERMANY) + self.spawnzone:GetCoordinate():MarkToAll("Spawnzone") + + -- Add FSM transitions. + self:AddTransition("*", "Start", "Running") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Request", "*") + self:AddTransition("*", "Delivered", "*") + + --- Triggers the FSM event "Start". Starts the warehouse. + -- @function [parent=#WAREHOUSE] Start + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Start" after a delay. Starts the warehouse. + -- @function [parent=#WAREHOUSE] __Start + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Status". Queue is updated and requests are executed. + -- @function [parent=#WAREHOUSE] Status + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Status" after a delay. Queue is updated and requests are executed. + -- @function [parent=#WAREHOUSE] __Status + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Request". Executes a request if possible. + -- @function [parent=#WAREHOUSE] Request + -- @param #WAREHOUSE self + -- @param #WAREHOUSE.Queueitem Request Information table of the request. + + --- Triggers the FSM event "Request" after a delay. Executes a request if possible. + -- @function [parent=#WAREHOUSE] __Request + -- @param #WAREHOUSE self + -- @param #number Delay Delay in seconds. + -- @param #WAREHOUSE.Queueitem Request Information table of the request. + + + --- Triggers the FSM event "Delivered". A group has been delivered from the warehouse to another airbase or warehouse. + -- @function [parent=#WAREHOUSE] Delivered + -- @param #WAREHOUSE self + -- @param Wrapper.Group#GROUP group Group that was delivered. + + --- Triggers the FSM event "Delivered" after a delay. A group has been delivered from the warehouse to another airbase or warehouse. + -- @function [parent=#WAREHOUSE] __Delivered + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param Wrapper.Group#GROUP group Group that was delivered. + + return self +end + +--- Set a zone where the (ground) assets of the warehouse are spawned once requested. +-- @param #WAREHOUSE self +-- @param Core.Zone#ZONE zone The spawn zone. +-- @return #WAREHOUSE self +function WAREHOUSE:SetSpawnZone(zone) + self.spawnzone=zone + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM states +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Warehouse +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterStart(From, Event, To) + self:E(self.wid..string.format("Starting warehouse at airbase %s, category %d, coalition %d.", self.homebase:GetName(), self.category, self.coalition)) + + -- handle events + -- event takeoff + -- event landing + -- event crash/dead + -- event base captured ==> change coalition ==> add assets to other coalition + + self:__Status(5) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Warehouse +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterStatus(From, Event, To) + self:E(self.wid..string.format("Checking warehouse status of airbase %s", self.homebase:GetName())) + + -- Create a mark with the current assets in stock. + if self.markerid~=nil then + trigger.action.removeMark(self.markerid) + end + local marktext="Warehouse stock:\n" + local text="Warehouse stock:\n" + + local _data=self:GetStockInfo(self.stock) + for _attribute,_count in pairs(_data) do + marktext=marktext..string.format("%s=%d, ", _attribute,_count) -- Dont use \n because too many make DCS crash! + text=text..string.format("%s = %d\n", _attribute,_count) + end + self.markerid=self.coordinate:MarkToCoalition(marktext, self.coalition, true) + + -- Debug output. + self:E(self.wid..text) + MESSAGE:New(text, 10):ToAllIf(self.Debug) + + -- Display complete list of stock itmes. + if self.Debug then + --self:_DisplayStockItems(self.stock) + end + + -- Print queue. + self:_PrintQueue() + + -- Check queue and handle requests if possible. + local request=self:_CheckQueue() + + -- Execute the request. If the request is really executed, it is also deleted from the queue. + if request then + --self:Request(request.airbase, request.assetdesc, request.assetdescval, request.nasset, request.transporttype, request.ntransport) + self:Request(request) + end + + -- Call status again in 30 sec. + self:__Status(10) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On before "Request" event. Checks if the request can be fullfilled. +-- @param #WAREHOUSE self +-- @param Wrapper.Airbase#AIRBASE Airbase airbase requesting supply. +-- @param #WAREHOUSE.Descriptor AssetDescriptor Descriptor describing the asset that is requested. +-- @param AssetDescriptorValue Value of the asset descriptor. Type depends on descriptor, i.e. could be a string, etc. +-- @param #number nAsset Number of groups requested that match the asset specification. +-- @param #WAREHOUSE.TransportType TransportType Type of transport. +-- @param #number nTransport Number of transport units requested. +-- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. +function WAREHOUSE:AddRequest(airbase, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) + + nAsset=nAsset or 1 + TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED + nTransport=nTransport or 1 + Prio=Prio or 50 + + --TODO: check that + -- if warehouse or requestor is a FARP, plane asset and transport not possible + -- if requestor or warehouse is a SHIP, APC transport not possible, SELFPROPELLED only for AIR/SHIP + -- etc. etc... + + local request_category=airbase:GetDesc().category + + if self.category==Airbase.Category.HELIPAD or request_category==Airbase.Category.HELIPAD then + if TransportType==WAREHOUSE.TransportType.AIRPLANE then + self:E("ERROR: incorrect request. Warehouse or requestor is FARP. No transport by plane possible!") + return + end + end + + -- Increase id. + self.queueid=self.queueid+1 + + -- Request queue table item. + local request={uid=self.queueid, prio=Prio, airbase=airbase, category=request_category, assetdesc=AssetDescriptor, assetdescval=AssetDescriptorValue, nasset=nAsset, transporttype=TransportType, ntransport=nTransport} + + -- Add request to queue. + table.insert(self.queue, request) +end + +---Sorts the queue and checks if the request can be fullfilled. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE.Queueitem Chosen request. +function WAREHOUSE:_CheckQueue() + + -- Sort queue wrt to first prio and then qid. + self:_SortQueue() + + ---@param #WAREHOUSE.Queueitem qitem + --@return #boolean True if request is okay. + local function checkrequest(qitem) + local okay=true + -- Check if number of requested assets is in stock. + local _instock=#self:_FilterStock(self.stock, qitem.assetdesc, qitem.assetdescval) + env.info(string.format("FF desc = %s val=%s number=%d", qitem.assetdesc, tostring(qitem.assetdescval),_instock)) + if qitem.nasset > _instock then + env.info("FF check queue nasset > instock okay=false") + okay=false + end + -- Check if enough transport units are in stock. + _instock=#self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, qitem.transporttype) + if qitem.ntransport > _instock then + env.info("FF check queue ntransport > instock okay=false") + okay=false + end + return okay + end + + -- Search for a request we can execute. + local request=nil --#WAREHOUSE.Queueitem + for _,_qitem in ipairs(self.queue) do + local qitem=_qitem --#WAREHOUSE.Queueitem + local okay=checkrequest(qitem) + if okay==true then + request=qitem + break + end + end + + -- Execute request. + return request +end + + +--- On before "Request" event. Checks if the request can be fullfilled. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #WAREHOUSE.Queueitem Request Information table of the request. +-- @return #boolean If true, request is granted. +function WAREHOUSE:onbeforeRequest(From, Event, To, Request) + --env.info(self.wid..string.format("Airbase %s requesting asset %s = %s.", Airbase:GetName(), tostring(AssetDescriptor), tostring(AssetDescriptorValue))) + + -- Distance from warehouse to requesting airbase. + local distance=self.coordinate:Get2DDistance(Request.airbase:GetCoordinate()) + + -- Filter the requested assets. + local _stockrequest=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval) + + -- Asset is not in stock ==> request denied. + if #_stockrequest < Request.nasset then + local text=string.format("Request denied! Not enough assets currently in stock. Requested %d < %d in stock.", Request.nasset, #_stockrequest) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + return false + end + + -- Get the attibute of the requested asset. + local _stockitem=_stockrequest[1] --#WAREHOUSE.Stockitem + local _assetattribute=self:_GetAttribute(_stockitem.templatename) + + + + -- Check that a transport unit is available. + if Request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then + local _instock=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, Request.transporttype) + if #_instock==0 then + local text=string.format("Request denied! No transport unit currently available.") + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + return false + end + end + + -- TODO: For aircraft check that a parking spot is available. + + return true +end + +--- On after "Request" event. Initiates the transport of the assets to the requesting airbase. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #WAREHOUSE.Queueitem Request Information table of the request. +function WAREHOUSE:onafterRequest(From, Event, To, Request) + --env.info(self.wid..string.format("Airbase %s requesting asset %s = %s.", Airbase:GetName(), tostring(AssetDescriptor), tostring(AssetDescriptorValue))) + + ---------------------------------------------------------------- + + -- New empty cargo set in case we need it. + local CargoGroups = SET_CARGO:New() + + --TODO: make nearradius depended on transport type and asset type. + local _loadradius=5000 + local _nearradius=35 + + -- Filter the requested assets. + local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval) + + -- Spawn the assets. + local _delid={} + local _spawngroups={} + local _cargotype + local _cargocategory + for i=1,Request.nasset do + + -- Get stock item. + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + + -- Find a random point within the spawn zone. + local spawncoord=self.spawnzone:GetRandomCoordinate() + + -- Alias of the group. Spawn with ALIAS here or DCS crashes! + local _alias=string.format("%s_AssetID-%04d_RequestID-%04d", _assetitem.templatename,_assetitem.id,Request.uid) + local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) + local _group=nil --Wrapper.Group#GROUP + + -- Set a marker for the spawned group. + spawncoord:MarkToAll(string.format("Spawnpoint %s",_alias)) + + if _assetitem.category==Group.Category.GROUND then + -- Spawn ground troops. + _group=_spawn:SpawnFromCoordinate(spawncoord) + env.info(string.format("FF spawning group %s", _alias)) + elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then + -- Spawn air units. + local _takeoff=SPAWN.Takeoff.Cold + local _terminal=AIRBASE.TerminalType.OpenBig + if _assetitem.attribute==WAREHOUSE.Attribute.FIGHTER then + _terminal=AIRBASE.TerminalType.FighterAircraft + elseif _assetitem.attribute==WAREHOUSE.Attribute.BOMBER then + _terminal=AIRBASE.TerminalType.OpenBig + end + _group=_spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil,_terminal, true) + elseif _assetitem.category==Group.Category.TRAIN then + + end + + if _group then + _spawngroups[i]=_group + _cargotype=_assetitem.attribute + _cargocategory=_assetitem.category + table.insert(_delid,_assetitem.id) + + if Request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then + local cargogroup = CARGO_GROUP:New(_group, _alias, _alias, _loadradius, _nearradius) + CargoGroups:AddCargo(cargogroup) + end + + end + end + + -- Delete spawned items from warehouse stock. + for _,_id in pairs(_delid) do + self:_DeleteStockItem(_id) + end + + ---------------------------------------------------------------- + + -- No transport unit requested. Assets go by themselfes. + if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then + + for _i,_spawngroup in pairs(_spawngroups) do + + local group=_spawngroup --Wrapper.Group#GROUP + local ToCoordinate=Request.airbase:GetZone():GetRandomCoordinate() + + if _cargocategory==Group.Category.GROUND then + self:_RouteGround(group, ToCoordinate) + elseif _cargocategory==Group.Category.AIRPLANE then + self:_RouteAir(group, Request.airbase) + elseif _cargocategory==Group.Category.HELICOPTER then + self:_RouteAir(group, Request.airbase) + elseif _cargocategory==Group.Category.SHIP then + + elseif _cargocategory==Group.Category.TRAIN then + + end + + end + + -- Delete request from queue. + self:_DeleteQueueItem(Request.uid) + + -- No cargo transport necessary. + return + end + + env.info("FF cargo set name(s) = "..CargoGroups:GetObjectNames()) + ---------------------------------------------------------------- + + local TransportSet = SET_GROUP:New() --:AddGroupsByName(Plane:GetName()) + + -- Pickup and depoly locations. + local PickupAirbaseSet = SET_AIRBASE:New():AddAirbase(self.homebase) + local DeployAirbaseSet = SET_AIRBASE:New():AddAirbase(Request.airbase) + local DeployZoneSet = SET_ZONE:New():FilterPrefixes("Deploy"):FilterStart() + --local bla=SET_ZONE:New():AddZonesByName(AddZoneNames) + local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER + + -- Filter the requested transport assets. + local _assetstock=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, Request.transporttype) + + -- Dependent on transport type, spawn the transports and set up the dispatchers. + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + + -- Spawn the transport groups. + local _delid={} + for i=1,Request.ntransport do + + -- Get stock item. + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + + -- Spawn with ALIAS here or DCS crashes! + local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + + -- Spawn plane at airport in uncontrolled state. + local _takeoff=SPAWN.Takeoff.Cold + local _terminal=AIRBASE.TerminalType.OpenBig + local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil,_terminal, false) + + if spawngroup then + -- Set state of warehouse so we can retrieve it later. + spawngroup:SetState(spawngroup, "WAREHOUSE", self) + + -- Add group to transportset. + TransportSet:AddGroup(spawngroup) + + table.insert(_delid,_assetitem.id) + end + end + + -- Delete spawned items from warehouse stock. + for _,_id in pairs(_delid) do + self:_DeleteStockItem(_id) + end + + -- Define dispatcher for this task. + CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) + + elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + + -- Spawn the transport groups. + local _delid={} + for i=1,Request.ntransport do + + -- Get stock item. + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + + -- Spawn with ALIAS here or DCS crashes! + local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + + -- Spawn plane at airport in uncontrolled state. + -- TODO: check terminal type. + local _takeoff=SPAWN.Takeoff.Hot + local _terminal=AIRBASE.TerminalType.HelicopterUsable + local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitUnControlled(false):SpawnAtAirbase(self.homebase,_takeoff, nil,_terminal, false) + + if spawngroup then + -- Set state of warehouse so we can retrieve it later. + spawngroup:SetState(spawngroup, "WAREHOUSE", self) + + -- Add group to transportset. + TransportSet:AddGroup(spawngroup) + + table.insert(_delid,_assetitem.id) + end + end + + -- Delete spawned items from warehouse stock. + for _,_id in pairs(_delid) do + self:_DeleteStockItem(_id) + end + + -- Define dispatcher for this task. + CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportSet, CargoGroups, DeployZoneSet) + + -- Home zone. + CargoTransport:SetHomeZone(self.spawnzone) + + elseif Request.transporttype==WAREHOUSE.TransportType.APC then + + -- Spawn the transport groups. + local _delid={} + for i=1,Request.ntransport do + + -- Get stock item. + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + + -- Spawn with ALIAS here or DCS crashes! + local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + + -- Spawn plane at airport in uncontrolled state. + local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):SpawnFromCoordinate(self.spawnzone:GetRandomCoordinate()) + + if spawngroup then + -- Set state of warehouse so we can retrieve it later. + spawngroup:SetState(spawngroup, "WAREHOUSE", self) + + -- Add group to transportset. + TransportSet:AddGroup(spawngroup) + + table.insert(_delid,_assetitem.id) + end + end + + -- Delete spawned items from warehouse stock. + for _,_id in pairs(_delid) do + self:_DeleteStockItem(_id) + end + + -- Define dispatcher for this task. + CargoTransport = AI_CARGO_DISPATCHER_APC:NewWithZones(TransportSet, CargoGroups, DeployZoneSet, 0) + + elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then + + self:E(self.wid.."ERROR: transport by train not supported yet!") + return + + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then + + self:E(self.wid.."ERROR: transport by ship not supported yet!") + return + + elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then + + self:E(self.wid.."ERROR: transport type selfpropelled was already handled above. We should not get here!") + return + + else + self:E(self.wid.."ERROR: unknown transport type!") + return + end + + + --- Function called when cargo has arrived and was unloaded. + function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo) + + env.info("FF: OnAfterUnloaded") + self:E({From=From}) + self:E({Event=Event}) + self:E({To=To}) + self:E({Carrier=Carrier}) + self:E({Cargo=Cargo}) + + -- Get group obejet. + local group=Cargo:GetObject() --Wrapper.Group#GROUP + + -- Get warehouse state. + local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + + -- Trigger Delivered event. + warehouse:__Delivered(1, group) + end + + -- Start dispatcher. + CargoTransport:__Start(5) + + -- Delete request from queue. + self:_DeleteQueueItem(Request.uid) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Warehouse +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group The group that was delivered. +function WAREHOUSE:onafterDelivered(From, Event, To, Group) + env.info("FF warehouse cargo delivered! Croutine to closest point on road") + local road=Group:GetCoordinate():GetClosestPointToRoad() + local speed=Group:GetSpeedMax()*0.6 + Group:RouteGroundTo(road, speed, "Off Road") +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Add an airplane group to the warehouse stock. +-- @param #WAREHOUSE self +-- @param #string templategroupname Name of the late activated template group as defined in the mission editor. +-- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. +-- @return #WAREHOUSE self +function WAREHOUSE:AddAsset(templategroupname, ngroups) + + -- Set default. + local n=ngroups or 1 + + local group=GROUP:FindByName(templategroupname) + + if group then + + local DCSgroup=group:GetDCSObject() + local DCSunit=DCSgroup:getUnit(1) + local DCSdesc=DCSunit:getDesc() + local DCSdisplay=DCSunit:getDesc().displayName + local DCScategory=DCSgroup:getCategory() + local DCStype=DCSunit:getTypeName() + + env.info(string.format("group name = %s", group:GetName())) + env.info(string.format("display name = %s", DCSdisplay)) + env.info(string.format("category = %s", DCScategory)) + env.info(string.format("type = %s", DCStype)) + self:E({desc=DCSdesc}) + + local attribute=self:_GetAttribute(templategroupname) + + -- Add this n times to the table. + for i=1,n do + local stockitem={} --#WAREHOUSE.Stockitem + self.assetid=self.assetid+1 + stockitem.id=self.assetid + stockitem.templatename=templategroupname + stockitem.category=DCScategory + stockitem.unittype=DCStype + stockitem.attribute=attribute + table.insert(self.stock, stockitem) + end + + else + -- Group name does not exist! + self:E(string.format("ERROR: Template group name not defined in the mission editor. Check the spelling! templategroupname=%s",tostring(templategroupname))) + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Helper functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Route ground units to destination. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP Group The ground group. +-- @param Core.Point#COORDINATE Coordinate of the destination. +-- @param #number Speed Speed in km/h to drive to the destination coordinate. Default is 60% of max possible speed the unit can go. +function WAREHOUSE:_RouteGround(Group, Coordinate, Speed) + + if Group and Group:IsAlive() then + + local _speed=Speed or Group:GetSpeedMax()*0.6 + + -- Create a + local Waypoints = Group:TaskGroundOnRoad(Coordinate, _speed, "Off Road", true) + + -- Task function triggering the arrived event. + local TaskFunction = Group:TaskFunction("WAREHOUSE._Arrived", self) + + -- Put task function on last waypoint. + local Waypoint = Waypoints[#Waypoints] + Group:SetTaskWaypoint( Waypoint, TaskFunction ) + + -- Route group to destination. + Group:Route(Waypoints, 1) + end +end + +--- Task function for last waypoint. Triggering the Delivered event. +-- @param Wrapper.Group#GROUP Group The group that arrived. +-- @param #WAREHOUSE self +function WAREHOUSE._Arrived(Group, self) + --Trigger delivered event. + self:__Delivered(1, Group) +end + +--- Route the airplane from one airbase another. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane Airplane group to be routed. +-- @param Wrapper.Airbase#AIRBASE ToAirbase Destination airbase. +-- @param #number Speed Speed in km/h. Default is 80% of max possible speed the group can do. +function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) + + if Aircraft and Aircraft:IsAlive() then + + -- Set takeoff type. + local Takeoff = SPAWN.Takeoff.Cold + + -- Get template of group. + local Template = Aircraft:GetTemplate() + + -- Nil check + if Template==nil then + return + end + + -- Waypoints of the route. + local Points={} + + -- To point. + local AirbasePointVec2 = ToAirbase:GetPointVec2() + local ToWaypoint = AirbasePointVec2:WaypointAir( + POINT_VEC3.RoutePointAltType.BARO, + "Land", + "Landing", + Speed or Aircraft:GetSpeedMax()*0.8 + ) + ToWaypoint["airdromeId"] = ToAirbase:GetID() + ToWaypoint["speed_locked"] = true + + -- Aibase id and category. + local AirbaseID = ToAirbase:GetID() + local AirbaseCategory = ToAirbase:GetDesc().category + + if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then + ToWaypoint.linkUnit = AirbaseID + ToWaypoint.helipadId = AirbaseID + ToWaypoint.airdromeId = nil + elseif AirbaseCategory == Airbase.Category.AIRDROME then + ToWaypoint.airdromeId = AirbaseID + ToWaypoint.helipadId = nil + ToWaypoint.linkUnit = nil + end + + -- Task function triggering the arrived event. + local Task = Aircraft:TaskFunction("WAREHOUSE._Arrived", self) + + -- or + --ToWaypoint.task=Aircraft:TaskCombo({Task}) + ToWaypoint.task={Task} + + -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. + Template.route.points[2] = ToWaypoint + + -- Respawn group at the current airbase. + Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) + + end +end + +--- Filter stock assets by table entry. +-- @param #WAREHOUSE self +-- @param #table stock Table holding all assets in stock of the warehouse. Each entry is of type @{#WAREHOUSE.Stockitem}. +-- @param #string item Descriptor +-- @param value Value of the descriptor. +-- @return #table Filtered stock items table. +function WAREHOUSE:_FilterStock(stock, item, value) + + -- Filtered array. + local filtered={} + + -- Loop over stock items. + for _i,_stock in ipairs(stock) do + if _stock[item]==value then + _stock.pos=_i + table.insert(filtered, _stock) + end + end + + return filtered +end + +--- Filter stock assets by table entry. +-- @param #WAREHOUSE self +-- @param #table stock Table holding all assets in stock of the warehouse. Each entry is of type @{#WAREHOUSE.Stockitem}. +function WAREHOUSE:_DisplayStockItems(stock) + + local text=self.wid..string.format("Warehouse %s stock assets:\n", self.homebase:GetName()) + for _,_stock in pairs(stock) do + local mystock=_stock --#WAREHOUSE.Stockitem + text=text..string.format("template = %s, category = %d, unittype = %s, attribute = %s\n", mystock.templatename, mystock.category, mystock.unittype, mystock.attribute) + end + + env.info(text) + MESSAGE:New(text, 10):ToAll() +end + +--- Check if a group has a generalized attribute. +-- @param #WAREHOUSE self +-- @param #string groupname Name of the group. +-- @param #WAREHOUSE.Attribute attribute Attribute to check. +-- @return #boolean True if group has the specified attribute. +function WAREHOUSE:_HasAttribute(groupname, attribute) + + local group=GROUP:FindByName(groupname) + + if group then + local groupattribute=self:_HasAttribute(groupname,attribute) + return groupattribute==attribute + end + + return false +end + +--- Get the generalized attribute of a group. +-- @param #WAREHOUSE self +-- @param #string groupname Name of the group. +-- @return #WAREHOUSE.Attribute Generalized attribute of the group. +function WAREHOUSE:_GetAttribute(groupname) + + local group=GROUP:FindByName(groupname) + + local attribute=WAREHOUSE.Attribute.OTHER --#WAREHOUSE.Attribute + + if group then + + -- Get generalized attributes. + -- Transports: Helos, planes and APCs + local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") + local transporthelo=group:HasAttribute("Transport helicopters") + local transportapc=group:HasAttribute("Infantry carriers") + local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") + local tanker=group:HasAttribute("Tankers") + local awacs=group:HasAttribute("AWACS") + local artillery=group:HasAttribute("Artillery") + local infantry=group:HasAttribute("Infantry") + local attackhelicopter=group:HasAttribute("Attack helicopters") + local bomber=group:HasAttribute("Bombers") + local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") + local truck=group:HasAttribute("Trucks") + + -- Debug output. + --[[ + env.info(string.format("transport pane = %s", tostring(transportplane))) + env.info(string.format("transport helo = %s", tostring(transporthelo))) + env.info(string.format("transport apc = %s", tostring(transportapc))) + env.info(string.format("figther = %s", tostring(fighter))) + env.info(string.format("tanker = %s", tostring(tanker))) + env.info(string.format("awacs = %s", tostring(awacs))) + env.info(string.format("artillery = %s", tostring(artillery))) + env.info(string.format("infantry = %s", tostring(infantry))) + env.info(string.format("attack helo = %s", tostring(attackhelicopter))) + env.info(string.format("bomber = %s", tostring(bomber))) + env.info(string.format("tank = %s", tostring(tank))) + env.info(string.format("truck = %s", tostring(truck))) + ]] + + if transportplane then + attribute=WAREHOUSE.Attribute.TRANSPORT_PLANE + elseif transporthelo then + attribute=WAREHOUSE.Attribute.TRANSPORT_HELO + elseif transportapc then + attribute=WAREHOUSE.Attribute.TRANSPORT_APC + elseif fighter then + attribute=WAREHOUSE.Attribute.FIGHTER + elseif tanker then + attribute=WAREHOUSE.Attribute.TANKER + elseif awacs then + attribute=WAREHOUSE.Attribute.AWACS + elseif artillery then + attribute=WAREHOUSE.Attribute.ARTILLERY + elseif infantry then + attribute=WAREHOUSE.Attribute.INFANTRY + elseif attackhelicopter then + attribute=WAREHOUSE.Attribute.ATTACKHELICOPTER + elseif bomber then + attribute=WAREHOUSE.Attribute.BOMBER + elseif tank then + attribute=WAREHOUSE.Attribute.TANK + elseif truck then + attribute=WAREHOUSE.Attribute.TRUCK + else + attribute=WAREHOUSE.Attribute.OTHER + end + + end + + return attribute +end + +--- Returns the number of assets for each generalized attribute. +-- @param #WAREHOUSE self +-- @param #table stock The stock of the warehouse. +-- @return #table Data table holding the numbers. +function WAREHOUSE:GetStockInfo(stock) + + local _data={} + for _j,_attribute in pairs(WAREHOUSE.Attribute) do + + local n=0 + for _i,_item in pairs(stock) do + local _ite=_item --#WAREHOUSE.Stockitem + if _ite.attribute==_attribute then + n=n+1 + end + end + + _data[_attribute]=n + end + + return _data +end + +--- Delete item from stock. +-- @param #WAREHOUSE self +-- @param #number _uid The unique id of the item to be deleted. +function WAREHOUSE:_DeleteStockItem(_uid) + for i=1,#self.stock do + local item=self.stock[i] --#WAREHOUSE.Stockitem + if item.id==_uid then + table.remove(self.stock,i) + break + end + end +end + +--- Delete item from queue. +-- @param #WAREHOUSE self +-- @param #number _uid The id of the item to be deleted. +function WAREHOUSE:_DeleteQueueItem(_uid) + env.info("FF BEFORE delete queue") + self:_PrintQueue() + for i=1,#self.queue do + local item=self.queue[i] --#WAREHOUSE.Queueitem + if item.uid==_uid then + table.remove(self.queue,i) + break + end + end + env.info("FF AFTER delete queue") + self:_PrintQueue() +end + +--- Sort requests queue wrt prio and request uid. +-- @param #WAREHOUSE self +function WAREHOUSE:_SortQueue() + self:F3() + -- Sort. + local function _sort(a, b) + return (a.prio < b.prio) or (a.prio==b.prio and a.uid < b.uid) + end + table.sort(self.queue, _sort) +end + +--- Prints the queue to DCS.log file. +-- @param #WAREHOUSE self +function WAREHOUSE:_PrintQueue() + env.info(self.wid.."Queue:") + for _,_qitem in ipairs(self.queue) do + local qitem=_qitem --#WAREHOUSE.Queueitem + local text=string.format("uid=%d, prio=%d, airbase=%s (category=%d), descriptor: %s=%s, nasssets=%d, transport=%s, ntransport=%d", + qitem.uid, qitem.prio, qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval),qitem.nasset,qitem.transporttype,qitem.ntransport) + env.info(text) + end +end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua index fd1d60ef7..ae4ebc7b8 100644 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ b/Moose Development/Moose/Tasking/Task_CARGO.lua @@ -589,6 +589,12 @@ do -- TASK_CARGO Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) + for _, Group in pairs( SetGroup:GetSet() ) do + for __, Unit in pairs( Group:GetUnits() ) do + local Unit = Unit -- Wrapper.Unit#UNIT + Unit:SetCargoBayWeightLimit() + end + end ---- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit @@ -610,7 +616,6 @@ do -- TASK_CARGO local TaskUnitName = TaskUnit:GetName() local MenuTime = Task:InitTaskControlMenu( TaskUnit ) local MenuControl = Task:GetTaskControlMenu( TaskUnit ) - local CargoItemCount = TaskUnit:CargoItemCount() Task.SetCargo:ForEachCargo( @@ -635,7 +640,13 @@ do -- TASK_CARGO local TaskGroup = TaskUnit:GetGroup() if Cargo:IsUnLoaded() then - if CargoItemCount < 1 then + local CargoBayFreeWeight = TaskUnit:GetCargoBayFreeWeight() + local CargoWeight = Cargo:GetWeight() + + self:F({CargoBayFreeWeight=CargoBayFreeWeight}) + + -- Only when there is space within the bay to load the next cargo item! + if CargoBayFreeWeight > CargoWeight then if Cargo:IsInReportRadius( TaskUnit:GetPointVec2() ) then local NotInDeployZones = true for DeployZoneName, DeployZone in pairs( Task.DeployZones ) do diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 9c32fb42c..70169576e 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -265,14 +265,14 @@ AIRBASE.PersianGulf = { -- -- Supported types are: -- --- * AIRBASE.TerminalType.Runway: Valid spawn points on runway. --- * AIRBASE.TerminalType.HelicopterOnly: Special spots for Helicopers. --- * AIRBASE.TerminalType.Shelter: Hardened Air Shelter. Currently only on Caucaus map. --- * AIRBASE.TerminalType.OpenMed: Open/Shelter air airplane only. --- * AIRBASE.TerminalType.OpenBig: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. --- * AIRBASE.TerminalType.OpenMedOrBig: Combines OpenMed and OpenBig spots. --- * AIRBASE.TerminalType.HelicopterUnsable: Combines HelicopterOnly, OpenMed and OpenBig. --- * AIRBASE.TerminalType.FighterAircraft: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. +-- * AIRBASE.TerminalType.Runway = 16: Valid spawn points on runway. +-- * AIRBASE.TerminalType.HelicopterOnly = 40: Special spots for Helicopers. +-- * AIRBASE.TerminalType.Shelter = 68: Hardened Air Shelter. Currently only on Caucaus map. +-- * AIRBASE.TerminalType.OpenMed = 72: Open/Shelter air airplane only. +-- * AIRBASE.TerminalType.OpenBig = 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. +-- * AIRBASE.TerminalType.OpenMedOrBig = 176: Combines OpenMed and OpenBig spots. +-- * AIRBASE.TerminalType.HelicopterUnsable = 216: Combines HelicopterOnly, OpenMed and OpenBig. +-- * AIRBASE.TerminalType.FighterAircraft = 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. -- @field TerminalType AIRBASE.TerminalType = { Runway=16, @@ -349,7 +349,7 @@ function AIRBASE.GetAllAirbases(coalition) local airbases={} for _,airbase in pairs(_DATABASE.AIRBASES) do - if (coalition~=nil and self:GetCoalition()==coalition) or coalition==nil then + if (coalition~=nil and airbase:GetCoalition()==coalition) or coalition==nil then table.insert(airbases, airbase) end end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 4a2596c49..81d26911e 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -121,13 +121,16 @@ GROUPTEMPLATE.Takeoff = { -- @return #GROUP self function GROUP:NewTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID ) local GroupName = GroupTemplate.name + _DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) - self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) - self:F2( GroupName ) + + local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) self.GroupName = GroupName - - _DATABASE:AddGroup( GroupName ) - + + if not _DATABASE.GROUPS[GroupName] then + _DATABASE.GROUPS[GroupName] = self + end + self:SetEventPriority( 4 ) return self end @@ -140,7 +143,6 @@ end -- @return #GROUP self function GROUP:Register( GroupName ) local self = BASE:Inherit( self, CONTROLLABLE:New( GroupName ) ) -- #GROUP - self:F( GroupName ) self.GroupName = GroupName self:SetEventPriority( 4 ) @@ -335,8 +337,7 @@ end --- Returns the country of the DCS Group. -- @param #GROUP self --- @return DCS#country.id The country identifier. --- @return #nil The DCS Group is not existing or alive. +-- @return DCS#country.id The country identifier or nil if the DCS Group is not existing or alive. function GROUP:GetCountry() self:F2( self.GroupName ) @@ -350,6 +351,40 @@ function GROUP:GetCountry() return nil end + +--- Check if at least one (or all) unit(s) has (have) a certain attribute. +-- See [hoggit documentation](https://wiki.hoggitworld.com/view/DCS_func_hasAttribute). +-- @param #GROUP self +-- @param #string attribute The name of the attribute the group is supposed to have. Valid attributes can be found in the "db_attributes.lua" file which is located at in "C:\Program Files\Eagle Dynamics\DCS World\Scripts\Database". +-- @param #boolean all If true, all units of the group must have the attribute in order to return true. Default is only one unit of a heterogenious group needs to have the attribute. +-- @return #boolean Group has this attribute. +function GROUP:HasAttribute(attribute, all) + + -- Get all units of the group. + local _units=self:GetUnits() + + local _allhave=true + local _onehas=false + + for _,_unit in pairs(_units) do + local _unit=_unit --Wrapper.Unit#UNIT + if _unit then + local _hastit=_unit:HasAttribute(attribute) + if _hastit==true then + _onehas=true + else + _allhave=false + end + end + end + + if all==true then + return _allhave + else + return _onehas + end +end + --- Returns the maximum speed of the group. -- If the group is heterogenious and consists of different units, the max speed of the slowest unit is returned. -- @param #GROUP self @@ -1339,97 +1374,100 @@ function GROUP:Respawn( Template, Reset ) end ---- @param Wrapper.Group#GROUP self -function GROUP:RespawnAtAirbase( AirbaseRespawn, Takeoff, TakeoffAltitude ) -- R2.4 - self:F( { AirbaseRespawn, Takeoff, TakeoffAltitude } ) - - local PointVec3 = AirbaseRespawn:GetPointVec3() +--- Respawn a group at an airbase. +-- Note that the group has to be on parking spots at the airbase already in order for this to work. +-- So each unit of the group is respawned at exactly the same parking spot as it currently occupies. +-- @param Wrapper.Group#GROUP self +-- @param #table SpawnTemplate (Optional) The spawn template for the group. If no template is given it is exacted from the group. +-- @param Core.Spawn#SPAWN.Takeoff Takeoff (Optional) Takeoff type. Sould be either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is SPAWN.Takeoff.Hot. +-- @param #boolean Uncontrolled (Optional) If true, spawn in uncontrolled state. +-- @return Wrapper.Group#GROUP Group spawned at airbase or nil if group could not be spawned. +function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- R2.4 + self:F2( { SpawnTemplate, Takeoff, Uncontrolled} ) + -- Get closest airbase. Should be the one we are currently on. + local airbase=self:GetCoordinate():GetClosestAirbase() + + if airbase then + self:F2("Closest airbase = "..airbase:GetName()) + else + self:E("ERROR: could not find closest airbase!") + return nil + end + -- Takeoff type. Default hot. Takeoff = Takeoff or SPAWN.Takeoff.Hot - local SpawnTemplate = self:GetTemplate() + -- Coordinate of the airbase. + local AirbaseCoord=airbase:GetCoordinate() + + -- Spawn template. + SpawnTemplate = SpawnTemplate or self:GetTemplate() if SpawnTemplate then - local SpawnPoint = SpawnTemplate.route.points[1] + local SpawnPoint = SpawnTemplate.route.points[1] -- These are only for ships. SpawnPoint.linkUnit = nil SpawnPoint.helipadId = nil SpawnPoint.airdromeId = nil - local AirbaseID = AirbaseRespawn:GetID() - local AirbaseCategory = AirbaseRespawn:GetDesc().category - self:F( { AirbaseCategory = AirbaseCategory, Ship = Airbase.Category.SHIP, Helipad = Airbase.Category.HELIPAD, Airdrome = Airbase.Category.AIRDROME } ) + -- Aibase id and category. + local AirbaseID = airbase:GetID() + local AirbaseCategory = airbase:GetDesc().category - if AirbaseCategory == Airbase.Category.SHIP then - SpawnPoint.linkUnit = AirbaseID - SpawnPoint.helipadId = AirbaseID - elseif AirbaseCategory == Airbase.Category.HELIPAD then - SpawnPoint.linkUnit = AirbaseID + if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then + SpawnPoint.linkUnit = AirbaseID SpawnPoint.helipadId = AirbaseID elseif AirbaseCategory == Airbase.Category.AIRDROME then SpawnPoint.airdromeId = AirbaseID end - SpawnPoint.alt = 0 - - SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type + SpawnPoint.alt = AirbaseCoord:GetLandHeight() + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action + -- Get the units of the group. + local units=self:GetUnits() - -- Translate the position of the Group Template to the Vec3. - for UnitID = 1, #SpawnTemplate.units do - self:T( 'Before Translation SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y ) + for UnitID,_unit in pairs(units) do + + local unit=_unit --Wrapper.Unit#UNIT - -- These cause a lot of confusion. - local UnitTemplate = SpawnTemplate.units[UnitID] - - UnitTemplate.parking = 15 - UnitTemplate.parking_id = "30" - UnitTemplate.alt = 0 - - local SX = UnitTemplate.x - local SY = UnitTemplate.y - local BX = SpawnPoint.x - local BY = SpawnPoint.y - local TX = PointVec3.x + ( SX - BX ) - local TY = PointVec3.z + ( SY - BY ) + -- Get closest parking spot of current unit. Note that we look for occupied spots since the unit is currently sitting on it! + local Parkingspot, TermialID, Distance=unit:GetCoordinate():GetClosestParkingSpot(airbase) - UnitTemplate.x = TX - UnitTemplate.y = TY - - if Takeoff == GROUP.Takeoff.Air then - UnitTemplate.alt = PointVec3.y + ( TakeoffAltitude or 200 ) - --else - -- UnitTemplate.alt = PointVec3.y + 10 - end - self:T( 'After Translation SpawnTemplate.units['..UnitID..'].x = ' .. UnitTemplate.x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. UnitTemplate.y ) - end - - SpawnPoint.x = PointVec3.x - SpawnPoint.y = PointVec3.z - - if Takeoff == GROUP.Takeoff.Air then - SpawnPoint.alt = PointVec3.y + ( TakeoffAltitude or 200 ) - --else - -- SpawnPoint.alt = PointVec3.y + 10 - end + --Parkingspot:MarkToAll("parking spot") + self:T2(string.format("Closest parking spot distance = %s, terminal ID=%s", tostring(Distance), tostring(TermialID))) - SpawnTemplate.x = PointVec3.x - SpawnTemplate.y = PointVec3.z - - local GroupSpawned = self:Respawn( SpawnTemplate ) - - -- When spawned in the air, we need to generate a Takeoff Event - - if Takeoff == GROUP.Takeoff.Air then - for UnitID, UnitSpawned in pairs( GroupSpawned:GetUnits() ) do - SCHEDULER:New( nil, BASE.CreateEventTakeoff, { GroupSpawned, timer.getTime(), UnitSpawned:GetDCSObject() } , 1 ) - end - end + -- Get unit coordinates for respawning position. + local uc=unit:GetCoordinate() + SpawnTemplate.units[UnitID].x = Parkingspot.x + SpawnTemplate.units[UnitID].y = Parkingspot.z + SpawnTemplate.units[UnitID].alt = Parkingspot.y - return GroupSpawned + SpawnTemplate.units[UnitID].parking = TermialID + SpawnTemplate.units[UnitID].parking_id = nil + + end + + SpawnPoint.x = AirbaseCoord.x + SpawnPoint.y = AirbaseCoord.z + + SpawnTemplate.x = AirbaseCoord.x + SpawnTemplate.y = AirbaseCoord.z + + -- Set uncontrolled state. + SpawnTemplate.uncontrolled=Uncontrolled + + -- Destroy and respawn. + self:Destroy() + _DATABASE:Spawn( SpawnTemplate ) + + -- Reset events. + self:ResetEvents() + + return self end return nil diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index 6e226a466..0f6e14f06 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -216,7 +216,7 @@ end function IDENTIFIABLE:GetDesc() self:F2( self.IdentifiableName ) - local DCSIdentifiable = self:GetDCSObject() + local DCSIdentifiable = self:GetDCSObject() -- DCS#Object if DCSIdentifiable then local IdentifiableDesc = DCSIdentifiable:getDesc() diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 23b85806a..b0200fd24 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -243,7 +243,7 @@ end --- Get the bounding box of the underlying POSITIONABLE DCS Object. -- @param #POSITIONABLE self --- @return DCS#Distance The bounding box of the POSITIONABLE. +-- @return DCS#Box3 The bounding box of the POSITIONABLE. -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetBoundingBox() --R2.1 self:F2() @@ -264,6 +264,29 @@ function POSITIONABLE:GetBoundingBox() --R2.1 end +--- Get the bounding radius of the underlying POSITIONABLE DCS Object. +-- @param #POSITIONABLE self +-- @return DCS#Distance The bounding radius of the POSITIONABLE. +-- @return #nil The POSITIONABLE is not existing or alive. +function POSITIONABLE:GetBoundingRadius() + self:F2() + + local Box = self:GetBoundingBox() + + + if Box then + local X = Box.max.x - Box.min.x + local Z = Box.max.z - Box.min.z + local CX = X / 2 + local CZ = Z / 2 + return math.max( CX, CZ ) + end + + BASE:E( { "Cannot GetBoundingRadius", Positionable = self, Alive = self:IsAlive() } ) + + return nil +end + --- Returns the altitude of the POSITIONABLE. -- @param Wrapper.Positionable#POSITIONABLE self -- @return DCS#Distance The altitude of the POSITIONABLE. @@ -323,7 +346,7 @@ end --- Returns the POSITIONABLE heading in degrees. -- @param Wrapper.Positionable#POSITIONABLE self --- @return #number The POSTIONABLE heading +-- @return #number The POSITIONABLE heading -- @return #nil The POSITIONABLE is not existing or alive. function POSITIONABLE:GetHeading() local DCSPositionable = self:GetDCSObject() @@ -347,6 +370,52 @@ function POSITIONABLE:GetHeading() return nil end +-- Is Methods + +--- Returns if the unit is of an air category. +-- If the unit is a helicopter or a plane, then this method will return true, otherwise false. +-- @param #POSITIONABLE self +-- @return #boolean Air category evaluation result. +function POSITIONABLE:IsAir() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) + + local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) + + self:T3( IsAirResult ) + return IsAirResult + end + + return nil +end + +--- Returns if the unit is of an ground category. +-- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. +-- @param #POSITIONABLE self +-- @return #boolean Ground category evaluation result. +function POSITIONABLE:IsGround() + self:F2() + + local DCSUnit = self:GetDCSObject() + + if DCSUnit then + local UnitDescriptor = DCSUnit:getDesc() + self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) + + local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) + + self:T3( IsGroundResult ) + return IsGroundResult + end + + return nil +end + --- Returns true if the POSITIONABLE is in the air. -- Polymorphic, is overridden in GROUP and UNIT. @@ -798,56 +867,148 @@ function POSITIONABLE:GetLaserCode() --R2.1 return self.LaserCode end ---- Add cargo. --- @param #POSITIONABLE self --- @param Core.Cargo#CARGO Cargo --- @return #POSITIONABLE -function POSITIONABLE:AddCargo( Cargo ) - self.__.Cargo[Cargo] = Cargo - return self -end +do -- Cargo ---- 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 --- @return #POSITIONABLE -function POSITIONABLE:RemoveCargo( Cargo ) - self.__.Cargo[Cargo] = nil - return self -end - ---- Returns if carrier has given cargo. --- @param #POSITIONABLE self --- @return Core.Cargo#CARGO Cargo -function POSITIONABLE:HasCargo( Cargo ) - return self.__.Cargo[Cargo] -end - ---- Clear all cargo. --- @param #POSITIONABLE self -function POSITIONABLE:ClearCargo() - self.__.Cargo = {} -end - ---- Get cargo item count. --- @param #POSITIONABLE self --- @return Core.Cargo#CARGO Cargo -function POSITIONABLE:CargoItemCount() - local ItemCount = 0 - for CargoName, Cargo in pairs( self.__.Cargo ) do - ItemCount = ItemCount + Cargo:GetCount() + --- Add cargo. + -- @param #POSITIONABLE self + -- @param Core.Cargo#CARGO Cargo + -- @return #POSITIONABLE + function POSITIONABLE:AddCargo( Cargo ) + self.__.Cargo[Cargo] = Cargo + return self end - return ItemCount -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 + -- @return #POSITIONABLE + function POSITIONABLE:RemoveCargo( Cargo ) + self.__.Cargo[Cargo] = nil + return self + end + + --- Returns if carrier has given cargo. + -- @param #POSITIONABLE self + -- @return Core.Cargo#CARGO Cargo + function POSITIONABLE:HasCargo( Cargo ) + return self.__.Cargo[Cargo] + end + + --- Clear all cargo. + -- @param #POSITIONABLE self + function POSITIONABLE:ClearCargo() + self.__.Cargo = {} + end + + --- Is cargo bay empty. + -- @param #POSITIONABLE self + function POSITIONABLE:IsCargoEmpty() + local IsEmpty = true + for _, Cargo in pairs( self.__.Cargo ) do + IsEmpty = false + break + end + return IsEmpty + end + + --- Get cargo item count. + -- @param #POSITIONABLE self + -- @return Core.Cargo#CARGO Cargo + function POSITIONABLE:CargoItemCount() + local ItemCount = 0 + for CargoName, Cargo in pairs( self.__.Cargo ) do + ItemCount = ItemCount + Cargo:GetCount() + end + return ItemCount + end + +-- --- Get Cargo Bay Free Volume in m3. +-- -- @param #POSITIONABLE self +-- -- @return #number CargoBayFreeVolume +-- function POSITIONABLE:GetCargoBayFreeVolume() +-- local CargoVolume = 0 +-- for CargoName, Cargo in pairs( self.__.Cargo ) do +-- CargoVolume = CargoVolume + Cargo:GetVolume() +-- end +-- return self.__.CargoBayVolumeLimit - CargoVolume +-- end +-- + --- Get Cargo Bay Free Weight in kg. + -- @param #POSITIONABLE self + -- @return #number CargoBayFreeWeight + function POSITIONABLE:GetCargoBayFreeWeight() + local CargoWeight = 0 + for CargoName, Cargo in pairs( self.__.Cargo ) do + CargoWeight = CargoWeight + Cargo:GetWeight() + end + return self.__.CargoBayWeightLimit - CargoWeight + end + +-- --- Get Cargo Bay Volume Limit in m3. +-- -- @param #POSITIONABLE self +-- -- @param #number VolumeLimit +-- function POSITIONABLE:SetCargoBayVolumeLimit( VolumeLimit ) +-- self.__.CargoBayVolumeLimit = VolumeLimit +-- end + + --- Get Cargo Bay Weight Limit in kg. + -- @param #POSITIONABLE self + -- @param #number WeightLimit + function POSITIONABLE:SetCargoBayWeightLimit( WeightLimit ) + if WeightLimit then + self.__.CargoBayWeightLimit = WeightLimit + else + -- If weightlimit is not provided, we will calculate it depending on the type of unit. + + -- When an airplane or helicopter, we calculate the weightlimit based on the descriptor. + if self:IsAir() then + local Desc = self:GetDesc() + self:F({Desc=Desc}) + self.__.CargoBayWeightLimit = Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) + else + local Desc = self:GetDesc() + + local Weights = { + ["M1126 Stryker ICV"] = 9, + ["M-113"] = 9, + ["AAV7"] = 25, + ["M2A1_halftrack"] = 9, + ["BMD-1"] = 9, + ["BMP-1"] = 8, + ["BMP-2"] = 7, + ["BMP-3"] = 8, + ["Boman"] = 25, + ["BTR-80"] = 9, + ["BTR_D"] = 12, + ["Cobra"] = 8, + ["LAV-25"] = 6, + ["M-2 Bradley"] = 6, + ["M1043 HMMWV Armament"] = 4, + ["M1045 HMMWV TOW"] = 4, + ["M1126 Stryker ICV"] = 9, + ["M1134 Stryker ATGM"] = 9, + ["Marder"] = 6, + ["MCV-80"] = 9, + ["MLRS FDDM"] = 4, + ["MTLB"] = 25, + ["TPZ"] = 10, + } + + local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 70 + self.__.CargoBayWeightLimit = CargoBayWeightLimit + end + end + end +end --- Cargo --- Signal a flare at the position of the POSITIONABLE. -- @param #POSITIONABLE self diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 753b842f4..059a0266c 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -854,51 +854,7 @@ end --- Is methods ---- Returns if the unit is of an air category. --- If the unit is a helicopter or a plane, then this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Air category evaluation result. -function UNIT:IsAir() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.AIRPLANE, Unit.Category.HELICOPTER } ) - - local IsAirResult = ( UnitDescriptor.category == Unit.Category.AIRPLANE ) or ( UnitDescriptor.category == Unit.Category.HELICOPTER ) - - self:T3( IsAirResult ) - return IsAirResult - end - - return nil -end - ---- Returns if the unit is of an ground category. --- If the unit is a ground vehicle or infantry, this method will return true, otherwise false. --- @param #UNIT self --- @return #boolean Ground category evaluation result. -function UNIT:IsGround() - self:F2() - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitDescriptor = DCSUnit:getDesc() - self:T3( { UnitDescriptor.category, Unit.Category.GROUND_UNIT } ) - - local IsGroundResult = ( UnitDescriptor.category == Unit.Category.GROUND_UNIT ) - - self:T3( IsGroundResult ) - return IsGroundResult - end - - return nil -end --- Returns if the unit is a friendly unit. -- @param #UNIT self diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index ea44b6326..d7da38110 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -57,6 +57,7 @@ Functional/ZoneCaptureCoalition.lua Functional/Artillery.lua Functional/Suppression.lua Functional/PseudoATC.lua +Functional/Warehouse.lua AI/AI_Balancer.lua AI/AI_A2A.lua