From a00311ed135e559d008fb5422d309d816fb1a7de Mon Sep 17 00:00:00 2001 From: FlightControl Date: Tue, 28 Mar 2017 12:20:15 +0200 Subject: [PATCH] Progress on Transport -- Created SET_CARGO -- DATABASE handles CARGO -- Event handles CARGO -- Event triggers CARGO events -- Menu system to pickup and deploy cargo -- Menu system to board and unboard cargo --- Moose Development/Moose/AI/AI_Cargo.lua | 56 ++- Moose Development/Moose/Core/Database.lua | 98 ++++- Moose Development/Moose/Core/Event.lua | 57 ++- Moose Development/Moose/Core/Set.lua | 343 ++++++++++++++++++ Moose Development/Moose/Moose.lua | 2 +- .../Moose/Tasking/Task_CARGO.lua | 221 +++++++++-- .../l10n/DEFAULT/Moose.lua | 2 +- Moose Mission Setup/Moose.lua | 2 +- .../TSK-100 - Cargo Pickup.lua | 4 +- .../TSK-100 - Cargo Pickup.miz | Bin 28410 -> 28460 bytes 10 files changed, 728 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index d978621f2..2e6f0d6ab 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -253,9 +253,39 @@ function AI_CARGO:New( Type, Name, Weight, ReportRadius, NearRadius ) CARGOS[self.Name] = self + self:SetEventPriority( 5 ) + return self end +--- Get the name of the Cargo. +-- @param #AI_CARGO self +-- @return #string The name of the Cargo. +function AI_CARGO:GetName() + return self.Name +end + +--- Get the type of the Cargo. +-- @param #AI_CARGO self +-- @return #string The type of the Cargo. +function AI_CARGO:GetType() + return self.Type +end + +--- Check if cargo is loaded. +-- @param #AI_CARGO self +-- @return #boolean true if loaded +function AI_CARGO:IsLoaded() + return self:Is( "Loaded" ) +end + +--- Check if cargo is unloaded. +-- @param #AI_CARGO self +-- @return #boolean true if unloaded +function AI_CARGO:IsUnLoaded() + return self:Is( "UnLoaded" ) +end + --- Template method to spawn a new representation of the AI_CARGO in the simulator. -- @param #AI_CARGO self @@ -398,6 +428,20 @@ function AI_CARGO_UNIT:New( CargoUnit, Type, Name, Weight, ReportRadius, NearRad self:T( self.ClassName ) + -- Cargo objects are added to the _DATABASE and SET_CARGO objects. + _EVENTDISPATCHER:CreateEventNewCargo( self ) + + return self +end + +--- AI_CARGO_UNIT Destructor. +-- @param #AI_CARGO_UNIT self +-- @return #AI_CARGO_UNIT +function AI_CARGO_UNIT:Destroy() + + -- Cargo objects are deleted from the _DATABASE and SET_CARGO objects. + _EVENTDISPATCHER:CreateEventDeleteCargo( self ) + return self end @@ -539,7 +583,7 @@ end -- @param #string From -- @param #string To -- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier ) +function AI_CARGO_UNIT:onenterBoarding( From, Event, To, CargoCarrier, ... ) self:F( { CargoCarrier.UnitName, From, Event, To } ) local Speed = 10 @@ -571,14 +615,14 @@ end -- @param #string From -- @param #string To -- @param Wrapper.Unit#UNIT CargoCarrier -function AI_CARGO_UNIT:onleaveBoarding( From, Event, To, CargoCarrier ) +function AI_CARGO_UNIT:onleaveBoarding( From, Event, To, CargoCarrier, ... ) self:F( { CargoCarrier.UnitName, From, Event, To } ) if self:IsNear( CargoCarrier:GetPointVec2() ) then - self:__Load( 1, CargoCarrier ) + self:__Load( 1, CargoCarrier, ... ) return true else - self:__Boarding( 1, CargoCarrier ) + self:__Boarding( 1, CargoCarrier, ... ) end return false end @@ -607,7 +651,7 @@ end -- @param #string Event -- @param #string From -- @param #string To -function AI_CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier ) +function AI_CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier, ... ) self:F() self.CargoInAir = self.CargoObject:InAir() @@ -617,7 +661,7 @@ function AI_CARGO_UNIT:onafterBoard( From, Event, To, CargoCarrier ) -- 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 - self:Load( CargoCarrier ) + self:Load( CargoCarrier, ... ) end end diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 3e9eea077..04f162690 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -6,12 +6,14 @@ -- =================================================== -- Mission designers can use the DATABASE class to refer to: -- +-- * STATICS -- * UNITS -- * GROUPS -- * CLIENTS --- * AIRPORTS +-- * AIRBASES -- * PLAYERSJOINED -- * PLAYERS +-- * CARGOS -- -- On top, for internal MOOSE administration purposes, the DATBASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. -- @@ -53,6 +55,7 @@ DATABASE = { PLAYERS = {}, PLAYERSJOINED = {}, CLIENTS = {}, + CARGOS = {}, AIRBASES = {}, COUNTRY_ID = {}, COUNTRY_NAME = {}, @@ -84,13 +87,15 @@ local _DATABASECategory = function DATABASE:New() -- Inherits from BASE - local self = BASE:Inherit( self, BASE:New() ) + local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE self:SetEventPriority( 1 ) self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) + self:HandleEvent( EVENTS.NewCargo ) + self:HandleEvent( EVENTS.DeleteCargo ) -- Follow alive players and clients self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) @@ -166,22 +171,24 @@ end --- Adds a Airbase based on the Airbase Name in the DATABASE. -- @param #DATABASE self -function DATABASE:AddAirbase( DCSAirbaseName ) +-- @param #string AirbaseName The name of the airbase +function DATABASE:AddAirbase( AirbaseName ) - if not self.AIRBASES[DCSAirbaseName] then - self.AIRBASES[DCSAirbaseName] = AIRBASE:Register( DCSAirbaseName ) + if not self.AIRBASES[AirbaseName] then + self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName ) end end --- Deletes a Airbase from the DATABASE based on the Airbase Name. -- @param #DATABASE self -function DATABASE:DeleteAirbase( DCSAirbaseName ) +-- @param #string AirbaseName The name of the airbase +function DATABASE:DeleteAirbase( AirbaseName ) - --self.AIRBASES[DCSAirbaseName] = nil + self.AIRBASES[AirbaseName] = nil end ---- Finds a AIRBASE based on the AirbaseName. +--- Finds an AIRBASE based on the AirbaseName. -- @param #DATABASE self -- @param #string AirbaseName -- @return Wrapper.Airbase#AIRBASE The found AIRBASE. @@ -191,6 +198,35 @@ function DATABASE:FindAirbase( AirbaseName ) return AirbaseFound end +--- Adds a Cargo based on the Cargo Name in the DATABASE. +-- @param #DATABASE self +-- @param #string CargoName The name of the airbase +function DATABASE:AddCargo( Cargo ) + + if not self.CARGOS[Cargo.Name] then + self.CARGOS[Cargo.Name] = Cargo + end +end + + +--- Deletes a Cargo from the DATABASE based on the Cargo Name. +-- @param #DATABASE self +-- @param #string CargoName The name of the airbase +function DATABASE:DeleteCargo( CargoName ) + + self.CARGOS[CargoName] = nil +end + +--- Finds an CARGO based on the CargoName. +-- @param #DATABASE self +-- @param #string CargoName +-- @return Wrapper.Cargo#CARGO The found CARGO. +function DATABASE:FindCargo( CargoName ) + + local CargoFound = self.CARGOS[CargoName] + return CargoFound +end + --- Finds a CLIENT based on the ClientName. -- @param #DATABASE self @@ -665,7 +701,7 @@ end --- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive UNIT in the database. The function needs to accept a UNIT parameter. +-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. -- @return #DATABASE self function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) @@ -677,7 +713,7 @@ end --- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive GROUP in the database. The function needs to accept a GROUP parameter. +-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a GROUP parameter. -- @return #DATABASE self function DATABASE:ForEachGroup( IteratorFunction, ... ) self:F2( arg ) @@ -690,7 +726,7 @@ end --- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an player in the database. The function needs to accept the player name. +-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept the player name. -- @return #DATABASE self function DATABASE:ForEachPlayer( IteratorFunction, ... ) self:F2( arg ) @@ -703,7 +739,7 @@ end --- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is was a player in the database. The function needs to accept a UNIT parameter. +-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. -- @return #DATABASE self function DATABASE:ForEachPlayerJoined( IteratorFunction, ... ) self:F2( arg ) @@ -715,7 +751,7 @@ end --- Iterate the DATABASE and call an iterator function for each CLIENT, providing the CLIENT to the function and optional parameters. -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter. +-- @param #function IteratorFunction The function that will be called object in the database. The function needs to accept a CLIENT parameter. -- @return #DATABASE self function DATABASE:ForEachClient( IteratorFunction, ... ) self:F2( arg ) @@ -725,6 +761,42 @@ function DATABASE:ForEachClient( IteratorFunction, ... ) return self end +--- Iterate the DATABASE and call an iterator function for each CARGO, providing the CARGO object to the function and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a CLIENT parameter. +-- @return #DATABASE self +function DATABASE:ForEachCargo( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.CARGOS ) + + return self +end + + +--- Handles the OnEventNewCargo event. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA EventData +function DATABASE:OnEventNewCargo( EventData ) + self:F2( { EventData } ) + + if EventData.Cargo then + self:AddCargo( EventData.Cargo ) + end +end + + +--- Handles the OnEventDeleteCargo. +-- @param #DATABASE self +-- @param Core.Event#EVENTDATA EventData +function DATABASE:OnEventDeleteCargo( EventData ) + self:F2( { EventData } ) + + if EventData.Cargo then + self:DeleteCargo( EventData.Cargo.Name ) + end +end + function DATABASE:_RegisterTemplates() self:F2() diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 39b0738af..7234188a9 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -197,6 +197,9 @@ EVENT = { ClassID = 0, } +world.event.S_EVENT_NEW_CARGO = world.event.S_EVENT_MAX + 1000 +world.event.S_EVENT_DELETE_CARGO = world.event.S_EVENT_MAX + 1001 + --- The different types of events supported by MOOSE. -- Use this structure to subscribe to events using the @{Base#BASE.HandleEvent}() method. -- @type EVENTS @@ -224,6 +227,8 @@ EVENTS = { PlayerComment = world.event.S_EVENT_PLAYER_COMMENT, ShootingStart = world.event.S_EVENT_SHOOTING_START, ShootingEnd = world.event.S_EVENT_SHOOTING_END, + NewCargo = world.event.S_EVENT_NEW_CARGO, + DeleteCargo = world.event.S_EVENT_DELETE_CARGO, } --- The Event structure @@ -271,6 +276,7 @@ EVENTS = { -- @field WeaponTgtDCSUnit + local _EVENTMETA = { [world.event.S_EVENT_SHOT] = { Order = 1, @@ -387,6 +393,16 @@ local _EVENTMETA = { Event = "OnEventShootingEnd", Text = "S_EVENT_SHOOTING_END" }, + [EVENTS.NewCargo] = { + Order = 1, + Event = "OnEventNewCargo", + Text = "S_EVENT_NEW_CARGO" + }, + [EVENTS.DeleteCargo] = { + Order = 1, + Event = "OnEventDeleteCargo", + Text = "S_EVENT_DELETE_CARGO" + }, } @@ -672,6 +688,39 @@ do -- OnEngineShutDown end +do -- Event Creation + + --- Creation of a New Cargo Event. + -- @param #EVENT self + -- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created. + function EVENT:CreateEventNewCargo( Cargo ) + self:F( { Cargo } ) + + local Event = { + id = EVENTS.NewCargo, + time = timer.getTime(), + cargo = Cargo, + } + + world.onEvent( Event ) + end + + --- Creation of a Cargo Deletion Event. + -- @param #EVENT self + -- @param AI.AI_Cargo#AI_CARGO Cargo The Cargo created. + function EVENT:CreateEventDeleteCargo( Cargo ) + self:F( { Cargo } ) + + local Event = { + id = EVENTS.DeleteCargo, + time = timer.getTime(), + cargo = Cargo, + } + + world.onEvent( Event ) + end + +end --- @param #EVENT self -- @param #EVENTDATA Event @@ -795,6 +844,11 @@ function EVENT:onEvent( Event ) --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end + if Event.cargo then + Event.Cargo = Event.cargo + Event.CargoName = Event.cargo.Name + end + local PriorityOrder = _EVENTMETA[Event.id].Order local PriorityBegin = PriorityOrder == -1 and 5 or 1 local PriorityEnd = PriorityOrder == -1 and 1 or 5 @@ -956,7 +1010,8 @@ function EVENT:onEvent( Event ) -- If the EventData is not bound to a specific unit, then call the EventClass EventFunction. -- Note that here the EventFunction will need to implement and determine the logic for the relevant source- or target unit, or weapon. - if (Event.IniDCSUnit or Event.WeaponUNIT) and not EventData.EventUnit then + if ( ( Event.IniDCSUnit or Event.WeaponUNIT) and not EventData.EventUnit ) or + Event.Cargo then if EventClass == EventData.EventClass then diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index bfb6ac69e..52e526061 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -2391,3 +2391,346 @@ function SET_AIRBASE:IsIncludeObject( MAirbase ) self:T2( MAirbaseInclude ) return MAirbaseInclude end + +--- @type SET_CARGO +-- @extends Core.Set#SET_BASE + +--- # SET_CARGO class, extends @{Set#SET_BASE} +-- +-- Mission designers can use the @{Set#SET_CARGO} class to build sets of cargos optionally belonging to certain: +-- +-- * Coalitions +-- * Types +-- * Name or Prefix +-- +-- ## SET_CARGO constructor +-- +-- Create a new SET_CARGO object with the @{#SET_CARGO.New} method: +-- +-- * @{#SET_CARGO.New}: Creates a new SET_CARGO object. +-- +-- ## Add or Remove CARGOs from SET_CARGO +-- +-- CARGOs can be added and removed using the @{Set#SET_CARGO.AddCargosByName} and @{Set#SET_CARGO.RemoveCargosByName} respectively. +-- These methods take a single CARGO name or an array of CARGO names to be added or removed from SET_CARGO. +-- +-- ## SET_CARGO filter criteria +-- +-- You can set filter criteria to automatically maintain the SET_CARGO contents. +-- Filter criteria are defined by: +-- +-- * @{#SET_CARGO.FilterCoalitions}: Builds the SET_CARGO with the cargos belonging to the coalition(s). +-- * @{#SET_CARGO.FilterPrefixes}: Builds the SET_CARGO with the cargos containing the prefix string(s). +-- * @{#SET_CARGO.FilterTypes}: Builds the SET_CARGO with the cargos belonging to the cargo type(s). +-- * @{#SET_CARGO.FilterCountries}: Builds the SET_CARGO with the cargos belonging to the country(ies). +-- +-- Once the filter criteria have been set for the SET_CARGO, you can start filtering using: +-- +-- * @{#SET_CARGO.FilterStart}: Starts the filtering of the cargos within the SET_CARGO. +-- +-- ## SET_CARGO iterators +-- +-- Once the filters have been defined and the SET_CARGO has been built, you can iterate the SET_CARGO with the available iterator methods. +-- The iterator methods will walk the SET_CARGO set, and call for each cargo within the set a function that you provide. +-- The following iterator methods are currently available within the SET_CARGO: +-- +-- * @{#SET_CARGO.ForEachCargo}: Calls a function for each cargo it finds within the SET_CARGO. +-- +-- @field #SET_CARGO SET_CARGO +-- +SET_CARGO = { + ClassName = "SET_CARGO", + Cargos = {}, + Filter = { + Coalitions = nil, + Types = nil, + Countries = nil, + ClientPrefixes = nil, + }, + FilterMeta = { + Coalitions = { + red = coalition.side.RED, + blue = coalition.side.BLUE, + neutral = coalition.side.NEUTRAL, + }, + }, +} + + +--- Creates a new SET_CARGO object, building a set of cargos belonging to a coalitions and categories. +-- @param #SET_CARGO self +-- @return #SET_CARGO self +-- @usage +-- -- Define a new SET_CARGO Object. The DatabaseSet will contain a reference to all Cargos. +-- DatabaseSet = SET_CARGO:New() +function SET_CARGO:New() + -- Inherits from BASE + local self = BASE:Inherit( self, SET_BASE:New( _DATABASE.CARGOS ) ) + + self:HandleEvent( EVENTS.NewCargo ) + self:HandleEvent( EVENTS.DeleteCargo ) + + return self +end + +--- Add CARGOs to SET_CARGO. +-- @param Core.Set#SET_CARGO self +-- @param #string AddCargoNames A single name or an array of CARGO names. +-- @return self +function SET_CARGO:AddCargosByName( AddCargoNames ) + + local AddCargoNamesArray = ( type( AddCargoNames ) == "table" ) and AddCargoNames or { AddCargoNames } + + for AddCargoID, AddCargoName in pairs( AddCargoNamesArray ) do + self:Add( AddCargoName, CARGO:FindByName( AddCargoName ) ) + end + + return self +end + +--- Remove CARGOs from SET_CARGO. +-- @param Core.Set#SET_CARGO self +-- @param Wrapper.Cargo#CARGO RemoveCargoNames A single name or an array of CARGO names. +-- @return self +function SET_CARGO:RemoveCargosByName( RemoveCargoNames ) + + local RemoveCargoNamesArray = ( type( RemoveCargoNames ) == "table" ) and RemoveCargoNames or { RemoveCargoNames } + + for RemoveCargoID, RemoveCargoName in pairs( RemoveCargoNamesArray ) do + self:Remove( RemoveCargoName.CargoName ) + end + + return self +end + + +--- Finds a Cargo based on the Cargo Name. +-- @param #SET_CARGO self +-- @param #string CargoName +-- @return Wrapper.Cargo#CARGO The found Cargo. +function SET_CARGO:FindCargo( CargoName ) + + local CargoFound = self.Set[CargoName] + return CargoFound +end + + + +--- Builds a set of cargos of coalitions. +-- Possible current coalitions are red, blue and neutral. +-- @param #SET_CARGO self +-- @param #string Coalitions Can take the following values: "red", "blue", "neutral". +-- @return #SET_CARGO self +function SET_CARGO:FilterCoalitions( Coalitions ) + if not self.Filter.Coalitions then + self.Filter.Coalitions = {} + end + if type( Coalitions ) ~= "table" then + Coalitions = { Coalitions } + end + for CoalitionID, Coalition in pairs( Coalitions ) do + self.Filter.Coalitions[Coalition] = Coalition + end + return self +end + +--- Builds a set of cargos of defined cargo types. +-- Possible current types are those types known within DCS world. +-- @param #SET_CARGO self +-- @param #string Types Can take those type strings known within DCS world. +-- @return #SET_CARGO self +function SET_CARGO:FilterTypes( Types ) + if not self.Filter.Types then + self.Filter.Types = {} + end + if type( Types ) ~= "table" then + Types = { Types } + end + for TypeID, Type in pairs( Types ) do + self.Filter.Types[Type] = Type + end + return self +end + + +--- Builds a set of cargos of defined countries. +-- Possible current countries are those known within DCS world. +-- @param #SET_CARGO self +-- @param #string Countries Can take those country strings known within DCS world. +-- @return #SET_CARGO self +function SET_CARGO:FilterCountries( Countries ) + if not self.Filter.Countries then + self.Filter.Countries = {} + end + if type( Countries ) ~= "table" then + Countries = { Countries } + end + for CountryID, Country in pairs( Countries ) do + self.Filter.Countries[Country] = Country + end + return self +end + + +--- Builds a set of cargos of defined cargo prefixes. +-- All the cargos starting with the given prefixes will be included within the set. +-- @param #SET_CARGO self +-- @param #string Prefixes The prefix of which the cargo name starts with. +-- @return #SET_CARGO self +function SET_CARGO:FilterPrefixes( Prefixes ) + if not self.Filter.CargoPrefixes then + self.Filter.CargoPrefixes = {} + end + if type( Prefixes ) ~= "table" then + Prefixes = { Prefixes } + end + for PrefixID, Prefix in pairs( Prefixes ) do + self.Filter.CargoPrefixes[Prefix] = Prefix + end + return self +end + + + +--- Starts the filtering. +-- @param #SET_CARGO self +-- @return #SET_CARGO self +function SET_CARGO:FilterStart() + + if _DATABASE then + self:_FilterStart() + end + + return self +end + + +--- Handles the Database to check on an event (birth) that the Object was added in the Database. +-- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! +-- @param #SET_CARGO self +-- @param Core.Event#EVENTDATA Event +-- @return #string The name of the CARGO +-- @return #table The CARGO +function SET_CARGO:AddInDatabase( Event ) + self:F3( { Event } ) + + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] +end + +--- Handles the Database to check on any event that Object exists in the Database. +-- This is required, because sometimes the _DATABASE event gets called later than the SET_BASE event or vise versa! +-- @param #SET_CARGO self +-- @param Core.Event#EVENTDATA Event +-- @return #string The name of the CARGO +-- @return #table The CARGO +function SET_CARGO:FindInDatabase( Event ) + self:F3( { Event } ) + + return Event.IniDCSUnitName, self.Database[Event.IniDCSUnitName] +end + +--- Iterate the SET_CARGO and call an interator function for each CARGO, providing the CARGO and optional parameters. +-- @param #SET_CARGO self +-- @param #function IteratorFunction The function that will be called when there is an alive CARGO in the SET_CARGO. The function needs to accept a CARGO parameter. +-- @return #SET_CARGO self +function SET_CARGO:ForEachCargo( IteratorFunction, ... ) + self:F2( arg ) + + self:ForEach( IteratorFunction, arg, self.Set ) + + return self +end + +--- Iterate the SET_CARGO while identifying the nearest @{Cargo#CARGO} from a @{Point#POINT_VEC2}. +-- @param #SET_CARGO self +-- @param Core.Point#POINT_VEC2 PointVec2 A @{Point#POINT_VEC2} object from where to evaluate the closest @{Cargo#CARGO}. +-- @return Wrapper.Cargo#CARGO The closest @{Cargo#CARGO}. +function SET_CARGO:FindNearestCargoFromPointVec2( PointVec2 ) + self:F2( PointVec2 ) + + local NearestCargo = self:FindNearestObjectFromPointVec2( PointVec2 ) + return NearestCargo +end + + + +--- +-- @param #SET_CARGO self +-- @param AI.AI_Cargo#AI_CARGO MCargo +-- @return #SET_CARGO self +function SET_CARGO:IsIncludeObject( MCargo ) + self:F2( MCargo ) + + local MCargoInclude = true + + if MCargo then + local MCargoName = MCargo:GetName() + + if self.Filter.Coalitions then + local MCargoCoalition = false + for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do + local CargoCoalitionID = MCargo:GetCoalition() + self:T3( { "Coalition:", CargoCoalitionID, self.FilterMeta.Coalitions[CoalitionName], CoalitionName } ) + if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == CargoCoalitionID then + MCargoCoalition = true + end + end + self:T( { "Evaluated Coalition", MCargoCoalition } ) + MCargoInclude = MCargoInclude and MCargoCoalition + end + + if self.Filter.Types then + local MCargoType = false + for TypeID, TypeName in pairs( self.Filter.Types ) do + self:T3( { "Type:", MCargo:GetType(), TypeName } ) + if TypeName == MCargo:GetType() then + MCargoType = true + end + end + self:T( { "Evaluated Type", MCargoType } ) + MCargoInclude = MCargoInclude and MCargoType + end + + if self.Filter.CargoPrefixes then + local MCargoPrefix = false + for CargoPrefixId, CargoPrefix in pairs( self.Filter.CargoPrefixes ) do + self:T3( { "Prefix:", string.find( MCargo.Name, CargoPrefix, 1 ), CargoPrefix } ) + if string.find( MCargo.Name, CargoPrefix, 1 ) then + MCargoPrefix = true + end + end + self:T( { "Evaluated Prefix", MCargoPrefix } ) + MCargoInclude = MCargoInclude and MCargoPrefix + end + end + + self:T2( MCargoInclude ) + return MCargoInclude +end + +--- Handles the OnEventNewCargo event for the Set. +-- @param #SET_CARGO self +-- @param Core.Event#EVENTDATA EventData +function SET_CARGO:OnEventNewCargo( EventData ) + + if EventData.Cargo then + if EventData.Cargo and self:IsIncludeObject( EventData.Cargo ) then + self:Add( EventData.Cargo.Name , EventData.Cargo ) + end + end +end + +--- Handles the OnDead or OnCrash event for alive units set. +-- @param #SET_CARGO self +-- @param Core.Event#EVENTDATA EventData +function SET_CARGO:OnEventDeleteCargo( EventData ) + self:F3( { EventData } ) + + if EventData.Cargo then + local Cargo = _DATABASE:FindCargo( EventData.Cargo.Name ) + if Cargo and Cargo.Name then + self:Remove( Cargo.Name ) + end + end +end + diff --git a/Moose Development/Moose/Moose.lua b/Moose Development/Moose/Moose.lua index 0312878a0..5195ff310 100644 --- a/Moose Development/Moose/Moose.lua +++ b/Moose Development/Moose/Moose.lua @@ -74,7 +74,7 @@ _EVENTDISPATCHER = EVENT:New() -- Core.Event#EVENT _SCHEDULEDISPATCHER = SCHEDULEDISPATCHER:New() -- Core.Timer#SCHEDULEDISPATCHER --- Declare the main database object, which is used internally by the MOOSE classes. -_DATABASE = DATABASE:New() -- Database#DATABASE +_DATABASE = DATABASE:New() -- Core.Database#DATABASE diff --git a/Moose Development/Moose/Tasking/Task_CARGO.lua b/Moose Development/Moose/Tasking/Task_CARGO.lua index b2ba8bce6..d44a8a920 100644 --- a/Moose Development/Moose/Tasking/Task_CARGO.lua +++ b/Moose Development/Moose/Tasking/Task_CARGO.lua @@ -66,14 +66,14 @@ do -- TASK_CARGO -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. - -- @param AI.AI_Cargo#AI_CARGO Cargo The cargo. + -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. -- @param #string TaskType The type of Cargo task. -- @return #TASK_CARGO self - function TASK_CARGO:New( Mission, SetGroup, TaskName, Cargo, TaskType ) + function TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, TaskType ) local self = BASE:Inherit( self, TASK:New( Mission, SetGroup, TaskName, TaskType ) ) -- #TASK_CARGO - self:F( {Mission, SetGroup, TaskName, Cargo, TaskType}) + self:F( {Mission, SetGroup, TaskName, SetCargo, TaskType}) - self.Cargo = Cargo + self.SetCargo = SetCargo self.TaskType = TaskType Mission:AddTask( self ) @@ -81,46 +81,125 @@ do -- TASK_CARGO local Fsm = self:GetUnitProcess() - Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "RouteToCargo", Rejected = "Reject" } ) + Fsm:AddProcess ( "Planned", "Accept", ACT_ASSIGN_ACCEPT:New( self.TaskBriefing ), { Assigned = "SelectAction", Rejected = "Reject" } ) - Fsm:AddTransition( "Assigned", "RouteToCargo", "RoutingToCargo" ) - Fsm:AddProcess ( "RoutingToCargo", "RouteToCargoPickup", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtCargo" } ) + Fsm:AddTransition( { "Assigned", "Landed", "Boarded", "Deployed" } , "SelectAction", "WaitingForCommand" ) + + Fsm:AddTransition( "WaitingForCommand", "RouteToPickup", "RoutingToPickup" ) + Fsm:AddProcess ( "RoutingToPickup", "RouteToPickupPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtPickup" } ) + Fsm:AddTransition( "Arrived", "ArriveAtPickup", "ArrivedAtPickup" ) + + Fsm:AddTransition( "WaitingForCommand", "RouteToDeploy", "RoutingToDeploy" ) + Fsm:AddProcess ( "RoutingToDeploy", "RouteToDeployPoint", ACT_ROUTE_POINT:New(), { Arrived = "ArriveAtDeploy" } ) + Fsm:AddTransition( "ArrivedAtDeploy", "ArriveAtDeploy", "ArrivedAtDeploy" ) - Fsm:AddTransition( { "Arrived", "RoutingToCargo" }, "ArriveAtCargo", "ArrivedAtCargo" ) - - Fsm:AddTransition( { "ArrivedAtCargo", "LandAtCargo" }, "Land", "Landing" ) + Fsm:AddTransition( { "ArrivedAtPickup", "ArrivedAtDeploy" }, "Land", "Landing" ) Fsm:AddTransition( "Landing", "Landed", "Landed" ) - Fsm:AddTransition( "OnGround", "PrepareBoarding", "AwaitBoarding" ) + Fsm:AddTransition( "WaitingForCommand", "PrepareBoarding", "AwaitBoarding" ) Fsm:AddTransition( "AwaitBoarding", "Board", "Boarding" ) Fsm:AddTransition( "Boarding", "Boarded", "Boarded" ) + + Fsm:AddTransition( "WaitingForCommand", "PrepareUnBoarding", "AwaitUnBoarding" ) + Fsm:AddTransition( "AwaitUnBoarding", "UnBoard", "UnBoarding" ) + Fsm:AddTransition( "UnBoarding", "UnBoarded", "UnBoarded" ) - Fsm:AddTransition( "Accounted", "DestroyedAll", "Accounted" ) - Fsm:AddTransition( "Accounted", "Success", "Success" ) + + Fsm:AddTransition( "Deployed", "Success", "Success" ) Fsm:AddTransition( "Rejected", "Reject", "Aborted" ) Fsm:AddTransition( "Failed", "Fail", "Failed" ) - - --- Route to Cargo - -- @param #FSM_PROCESS self - -- @param Wrapper.Unit#UNIT TaskUnit - -- @param Tasking.Task_CARGO#TASK_CARGO Task - function Fsm:onafterRouteToCargo( TaskUnit, Task ) - self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - - - Task:SetCargoPickup( Task.Cargo, TaskUnit ) - self:__RouteToCargoPickup( 0.1 ) - end --- -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_CARGO#TASK_CARGO Task - function Fsm:OnAfterArriveAtCargo( TaskUnit, Task ) + function Fsm:OnEnterWaitingForCommand( TaskUnit, Task ) self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - if Task.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + TaskUnit.Menu = MENU_GROUP:New( TaskUnit:GetGroup(), Task:GetName() .. " @ " .. TaskUnit:GetName() ) + + Task.SetCargo:ForEachCargo( + + --- @param AI.AI_Cargo#AI_CARGO Cargo + function( Cargo ) + if Cargo:IsUnLoaded() then + if Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + MENU_GROUP_COMMAND:New( + TaskUnit:GetGroup(), + "Pickup cargo " .. Cargo.Name, + TaskUnit.Menu, + self.MenuBoardCargo, + self, + Cargo + ) + else + MENU_GROUP_COMMAND:New( + TaskUnit:GetGroup(), + "Route to cargo " .. Cargo.Name, + TaskUnit.Menu, + self.MenuRouteToPickup, + self, + Cargo + ) + end + end + + if Cargo:IsLoaded() then + MENU_GROUP_COMMAND:New( + TaskUnit:GetGroup(), + "Deploy cargo " .. Cargo.Name, + TaskUnit.Menu, + self.MenuBoardCargo, + self, + Cargo + ) + end + + end + ) + end + + --- + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:OnLeaveWaitingForCommand( TaskUnit, Task ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + TaskUnit.Menu:Remove() + end + + function Fsm:MenuBoardCargo( Cargo ) + self:__PrepareBoarding( 1.0, Cargo ) + end + + function Fsm:MenuRouteToPickup( Cargo ) + self:__RouteToPickup( 1.0, Cargo ) + end + + --- Route to Cargo + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:onafterRouteToPickup( TaskUnit, Task, From, Event, To, Cargo ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + + self.Cargo = Cargo + Task:SetCargoPickup( self.Cargo, TaskUnit ) + self:__RouteToPickupPoint( 0.1 ) + end + + + --- + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:OnAfterArriveAtPickup( TaskUnit, Task ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then self:__Land( -0.1 ) else self:__ArriveAtCargo( -10 ) @@ -131,10 +210,10 @@ do -- TASK_CARGO -- @param #FSM_PROCESS self -- @param Wrapper.Unit#UNIT TaskUnit -- @param Tasking.Task_CARGO#TASK_CARGO Task - function Fsm:OnAfterLand( TaskUnit, Task ) + function Fsm:OnAfterLand( TaskUnit, Task, From, Event, To ) self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) - if Task.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then if TaskUnit:InAir() then Task:GetMission():GetCommandCenter():MessageToGroup( "Land", TaskUnit:GetGroup(), "Land" ) self:__Land( -10 ) @@ -146,6 +225,82 @@ do -- TASK_CARGO self:__ArriveAtCargo( -0.1 ) end end + + --- + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:OnAfterLanded( TaskUnit, Task ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + if TaskUnit:InAir() then + self:__Land( -0.1 ) + else + Task:GetMission():GetCommandCenter():MessageToGroup( "Preparing to board in 10 seconds ...", TaskUnit:GetGroup(), "Boarding" ) + self:__PrepareBoarding( -10 ) + end + else + self:__ArriveAtCargo( -0.1 ) + end + end + + --- + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:OnAfterPrepareBoarding( TaskUnit, Task, From, Event, To, Cargo ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + self.Cargo = Cargo + if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + if TaskUnit:InAir() then + self:__Land( -0.1 ) + else + self:__Board( -0.1 ) + end + else + self:__ArriveAtCargo( -0.1 ) + end + end + + --- + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:OnAfterBoard( TaskUnit, Task ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + function self.Cargo:OnEnterLoaded( From, Event, To, TaskUnit, TaskProcess ) + + self:E({From, Event, To, TaskUnit, TaskProcess }) + + TaskProcess:__Boarded( 0.1 ) + + end + + + if self.Cargo:IsInRadius( TaskUnit:GetPointVec2() ) then + if TaskUnit:InAir() then + self:__Land( -0.1 ) + else + Task:GetMission():GetCommandCenter():MessageToGroup( "Boarding ...", TaskUnit:GetGroup(), "Boarding" ) + self.Cargo:Board( TaskUnit, self ) + end + else + self:__ArriveAtCargo( -0.1 ) + end + end + + --- + -- @param #FSM_PROCESS self + -- @param Wrapper.Unit#UNIT TaskUnit + -- @param Tasking.Task_CARGO#TASK_CARGO Task + function Fsm:OnAfterBoarded( TaskUnit, Task ) + self:E( { TaskUnit = TaskUnit, Task = Task and Task:GetClassNameAndID() } ) + + Task:GetMission():GetCommandCenter():MessageToGroup( "Boarded ...", TaskUnit:GetGroup(), "Boarding" ) + end return self @@ -165,7 +320,7 @@ do -- TASK_CARGO self:F({Cargo, TaskUnit}) local ProcessUnit = self:GetUnitProcess( TaskUnit ) - local ActRouteCargo = ProcessUnit:GetProcess( "RoutingToCargo", "RouteToCargoPickup" ) -- Actions.Act_Route#ACT_ROUTE_POINT + local ActRouteCargo = ProcessUnit:GetProcess( "RoutingToPickup", "RouteToPickupPoint" ) -- Actions.Act_Route#ACT_ROUTE_POINT ActRouteCargo:SetPointVec2( Cargo:GetPointVec2() ) ActRouteCargo:SetRange( Cargo:GetBoardingRange() ) return self @@ -284,10 +439,10 @@ do -- TASK_CARGO_TRANSPORT -- @param Tasking.Mission#MISSION Mission -- @param Set#SET_GROUP SetGroup The set of groups for which the Task can be assigned. -- @param #string TaskName The name of the Task. - -- @param AI_Cargo#AI_CARGO Cargo The cargo. + -- @param Core.Set#SET_CARGO SetCargo The scope of the cargo to be transported. -- @return #TASK_CARGO_TRANSPORT self - function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, Cargo ) - local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, Cargo, "TRANSPORT" ) ) -- #TASK_CARGO_TRANSPORT + function TASK_CARGO_TRANSPORT:New( Mission, SetGroup, TaskName, SetCargo ) + local self = BASE:Inherit( self, TASK_CARGO:New( Mission, SetGroup, TaskName, SetCargo, "Transport" ) ) -- #TASK_CARGO_TRANSPORT self:F() return self diff --git a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua index 4db9338c3..3b2b611ed 100644 --- a/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua +++ b/Moose Mission Setup/Moose Mission Update/l10n/DEFAULT/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170325_0745' ) +env.info( 'Moose Generation Timestamp: 20170328_0728' ) local base = _G diff --git a/Moose Mission Setup/Moose.lua b/Moose Mission Setup/Moose.lua index 4db9338c3..3b2b611ed 100644 --- a/Moose Mission Setup/Moose.lua +++ b/Moose Mission Setup/Moose.lua @@ -1,5 +1,5 @@ env.info( '*** MOOSE DYNAMIC INCLUDE START *** ' ) -env.info( 'Moose Generation Timestamp: 20170325_0745' ) +env.info( 'Moose Generation Timestamp: 20170328_0728' ) local base = _G diff --git a/Moose Test Missions/TSK - Task Modelling/TSK-100 - Cargo Pickup/TSK-100 - Cargo Pickup.lua b/Moose Test Missions/TSK - Task Modelling/TSK-100 - Cargo Pickup/TSK-100 - Cargo Pickup.lua index 6ec266dd7..363e7fcdc 100644 --- a/Moose Test Missions/TSK - Task Modelling/TSK-100 - Cargo Pickup/TSK-100 - Cargo Pickup.lua +++ b/Moose Test Missions/TSK - Task Modelling/TSK-100 - Cargo Pickup/TSK-100 - Cargo Pickup.lua @@ -26,8 +26,10 @@ do CargoEngineer = UNIT:FindByName( "Engineer" ) InfantryCargo = AI_CARGO_UNIT:New( CargoEngineer, "Engineer", "Engineer Sven", "81", 500, 25 ) + + SetCargo = SET_CARGO:New():FilterTypes( "Engineer" ):FilterStart() - Task_Cargo_Pickup = TASK_CARGO_TRANSPORT:New( Mission, TransportHelicopters, "Transport.001", InfantryCargo ) + Task_Cargo_Pickup = TASK_CARGO_TRANSPORT:New( Mission, TransportHelicopters, "Transport.001", SetCargo ) end diff --git a/Moose Test Missions/TSK - Task Modelling/TSK-100 - Cargo Pickup/TSK-100 - Cargo Pickup.miz b/Moose Test Missions/TSK - Task Modelling/TSK-100 - Cargo Pickup/TSK-100 - Cargo Pickup.miz index 834f0ccfd46154264b8b32b12d2ac93b6edef4d7..a06b1f4d141b36bc1a151176192ca167d3a8229d 100644 GIT binary patch delta 11784 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 W#WX3|Q5?J^X~~foM&I^7RsRPG`?Jyj delta 11749 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