diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e9cb77b8c..3af2025c4 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1,4 +1,4 @@ ---- **Functional** - (R2.5) - Simulation of logistics. +--- **Functional** - (R2.5) - Simulation of logistic operations. -- -- The MOOSE warehouse concept simulates the organization and implementation of complex operations regarding the flow of assets between the point of origin and the point of consumption -- in order to meet requirements of a potential conflict. In particular, this class is concerned with maintaining army supply lines while disrupting those of the enemy, since an armed @@ -136,14 +136,14 @@ -- Assets can be added to the warehouse stock by using the @{#WAREHOUSE.AddAsset}(*group*, *ngroups*, *forceattribute*) function. The parameter *group* has to be a MOOSE @{Wrapper.Group#GROUP}. -- The parameter *ngroups* specifies how many clones of this group are added to the stock. -- --- Note that the group should be a late activated template group, which was defined in the mission editor. -- -- infrantry=GROUP:FindByName("Some Infantry Group") -- warehouse:AddAsset(infantry, 5) -- --- This will add five infantry groups to the warehouse stock. +-- This will add five infantry groups to the warehouse stock. Note that the group will normally be a late activated template group, +-- which was defined in the mission editor. But you can also add other groups which are already spawned and present in the mission. -- --- Note that you can also add assets with a delay by using the @{#WAREHOUSE.__AddAsset}(*delay*, *group*, *ngroups*, *foceattribute*), where *delay* is the delay in seconds before the asset is added. +-- You can add assets with a delay by using the @{#WAREHOUSE.__AddAsset}(*delay*, *group*, *ngroups*, *foceattribute*), where *delay* is the delay in seconds before the asset is added. -- -- By default, the generalized attribute of the asset is determined automatically from the DCS descriptor attributes. However, this might not always result in the desired outcome. -- Therefore, it is possible, to force a generalized attribute for the asset with the third optional parameter *forceattribute*, which is of type @{#WAREHOUSE.Attribute}. @@ -233,10 +233,11 @@ -- -- end -- --- The variable *groupset* is a @{Core.Set#SET_GOUP} object and holds all asset groups from the request. The code above shows, how the mission designer can access the groups +-- The variable *groupset* is a @{Core.Set#SET_GROUP} object and holds all asset groups from the request. The code above shows, how the mission designer can access the groups -- for further tasking. Here, the groups are only smoked but, of course, you can use them for whatever task you fancy. -- -- Note that airborne groups are spawned in uncontrolled state and need to be activated first before they can start their assigned mission. +-- This can be done with the @{Wrapper.Controllable#CONTROLLABLE.StartUncontrolled} function. -- -- === -- @@ -369,6 +370,7 @@ -- -- A warehouse has a limited speed to process requests. Each time the status of the warehouse is updated only one requests is processed. -- The time interval between status updates is 30 seconds by default and can be adjusted via the @{#WAREHOUSE.SetStatusUpdate}(*interval*) function. +-- However, the status is also updated on other occasions, e.g. when a new request was added. -- -- === -- @@ -872,7 +874,9 @@ WAREHOUSE = { -- @field #number timestamp Absolute mission time in seconds when the request was processed. -- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. -- @field #number ndelivered Number of groups delivered to destination. --- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. +-- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport carrier groups. +-- @field Core.Set#SET_CARGO transportcargoset Set of cargo objects. +-- @field #table carriercargo Table holding the cargo groups of each carrier unit. -- @field #number ntransporthome Number of transports back home. -- @extends #WAREHOUSE.Queueitem @@ -987,7 +991,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.1" +WAREHOUSE.version="0.4.1w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1107,8 +1111,8 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. self:AddTransition("Attacked", "SelfRequest", "*") -- Request to warehouse itself. Also possible when warehouse is under attack! - self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests. - self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again. + self:AddTransition("Running", "Pause", "Paused") -- DONE Pause the processing of new requests. Still possible to add assets and requests. + self:AddTransition("Paused", "Unpause", "Running") -- DONE Unpause the warehouse. Queued requests are processed again. self:AddTransition("*", "Stop", "Stopped") -- DONE Stop the warehouse. self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. self:AddTransition("*", "Attacked", "Attacked") -- DONE Warehouse is under attack by enemy coalition. @@ -2069,40 +2073,6 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:__Status(-self.dTstatus) end ---- Count alive and filter dead groups. --- @param #WAREHOUSE self --- @param Core.Set#SET_GROUP groupset Set of groups. Dead groups are removed from the set. --- @return #number Number of alive groups. Returns zero if groupset is nil. -function WAREHOUSE:_FilterDead(groupset) - - local nalive=0 - - if groupset then - - -- Check if groups are still alive - local dead={} - for _,group in pairs(groupset:GetSetObjects()) do - if group and group:IsAlive() then - nalive=nalive+1 - else - table.insert(dead, group) - end - end - - -- TODO: Since the cargo groups are de- and re-spawned, does the counting for cargo groups actually work, when they are transported? - - -- Debug info. - self:T(self.wid..string.format("FilterDead: Alive=%d, Dead=%d", nalive, #dead)) - - -- Remove dead groups - local NoTriggerEvent=false - for _,group in pairs(dead) do - groupset:Remove(group, NoTriggerEvent) - end - end - - return nalive -end --- Function that checks if a pending job is done and can be removed from queue -- @param #WAREHOUSE self @@ -2113,20 +2083,17 @@ function WAREHOUSE:_JobDone() for _,request in pairs(self.pending) do local request=request --#WAREHOUSE.Pendingitem - -- Count number of cargo groups still alive and filter out dead groups. + -- Count number of cargo groups. local ncargo=0 if request.cargogroupset then ncargo=request.cargogroupset:Count() end - --=self:_FilterDead(request.cargogroupset) - -- Count number of transport groups (if any) and filter out dead groups. Dead groups are removed from the set. + -- Count number of transport groups (if any). local ntransport=0 if request.transportgroupset then request.transportgroupset:Count() - end - --self:_FilterDead(request.transportgroupset) - + end if ncargo==0 and ntransport==0 then @@ -3091,21 +3058,21 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then - self:E(self.wid.."ERROR: cargo transport by train not supported yet!") + self:_ErrorMessage("ERROR: Cargo transport by train not supported yet!") return elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then - self:E(self.wid.."ERROR: cargo transport by ship not supported yet!") + self:_ErrorMessage("ERROR: Cargo 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!") + self:_ErrorMessage("ERROR: Transport type selfpropelled was already handled above. We should not get here!") return else - self:E(self.wid.."ERROR: unknown transport type!") + self:_ErrorMessage("ERROR: Unknown transport type!") return end @@ -3130,8 +3097,19 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:_DeleteStockItem(_item) end - -- Add cargo groups to request. + -- Add transport groups, i.e. the carriers to request. Pending.transportgroupset=TransportSet + + -- Add cargo group set. + Pending.transportcargoset=CargoGroups + + -- Create empty tables which will be filled with the cargo groups of each carrier unit. Needed in case a carrier unit dies. + Pending.carriercargo={} + for _,carriergroup in pairs(TransportSet:GetObject()) do + for _,carrierunit in pairs(carriergroup:GetUnits()) do + Pending.carriercargo[carrierunit:GetName()]={} + end + end -- Add request to pending queue. table.insert(self.pending, Pending) @@ -3202,11 +3180,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Dispatcher Event Functions -- -------------------------------- - function CargoTransport:OnAfterLoaded(From,Event,To,CarrierGroup,Cargo,CarrierUnit,PickupZone) - local text=string.format("FF Carrier group %s loaded cargo %s into unit %s in pickup zone %s", CarrierGroup:GetName(), Cargo:GetObject():GetName(), CarrierUnit:GetName(), PickupZone:GetName()) - env.info(text) - end - function CargoTransport:OnAfterPickedUp(From,Event,To,CarrierGroup,PickupZone) local text=string.format("FF Carrier group %s picked up event in pickup zone %s.", CarrierGroup:GetName(), PickupZone:GetName()) env.info(text) @@ -3222,6 +3195,28 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) env.info(text) end + --- Function called when a carrier unit has loaded a cargo group. + function CargoTransport:OnAfterLoaded(From, Event, To, Carrier, Cargo, CarrierUnit, PickupZone) + local text=string.format("FF Carrier group %s loaded cargo %s into unit %s in pickup zone %s", CarrierGroup:GetName(), Cargo:GetObject():GetName(), CarrierUnit:GetName(), PickupZone:GetName()) + env.info(text) + + -- Get group object. + local group=Cargo:GetObject() --Wrapper.Group#GROUP + + -- Get warehouse state. + local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + + local text=string.format("FF Group %s was loaded into carrier %s.", tostring(group:GetName()), tostring(Carrier:GetName())) + env.info(text) + + -- Get request. + local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) + + -- Add cargo group to this carrier. + table.insert(request.carriercargo[CarrierUnit:GetName()], group) + + end + --- Function called when cargo has arrived and was unloaded. function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone) @@ -4182,32 +4177,160 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) -- Check if an asset unit was destroyed. if EventData and EventData.IniGroup then + local group=EventData.IniGroup + + -- Get warehouse, asset and request IDs from the group name. local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then self:T(self.wid..string.format("Warehouse %s captured event dead or crash of its asset unit %s.", self.alias, EventData.IniUnitName)) + -- Loop over all pending requests and get the one belonging to this unit. for _,request in pairs(self.pending) do local request=request --#WAREHOUSE.Pendingitem if request.uid==rid then - -- Count number of cargo groups still alive and filter out dead groups. Dead groups are removed from the set. - local ncargo=self:_FilterDead(request.cargogroupset) - - -- Count number of transport groups (if any) and filter out dead groups. Dead groups are removed from the set. - local ntransport=self:_FilterDead(request.transportgroupset) - - self:T(self.wid..string.format("Asset groups left for request id=%d: ncargo=%d, ntransport=%d", rid, ncargo, ntransport)) + + -- Update cargo and transport group sets of this request. We need to know if this job is finished. + self:_UnitDead(EventData.IniUnit, request) + + --[[ + if self:_GroupIsTransport(group, request) then + -- Count number of transport groups (if any) and filter out dead groups. Dead groups are removed from the set. + -- TODO: Now, what about the cargo groups that are inside the transport?! Need to save in which carrier the cargo is? + self:_FilterDead(request.transportgroupset) + else + -- Count number of cargo groups still alive and filter out dead groups. Dead groups are removed from the set. + self:_FilterDead(request.cargogroupset) + end + ]] end - end - - + end end end - end +--- A unit of a group just died. Update group sets in request. +-- This is important in order to determine if a job is done and can be removed from the (pending) queue. +-- @param #WAREHOUSE self +-- @param Wrapper.Unit#UNIT deadunit Unit that died. +-- @param #WAREHOUSE.Pendingitem request Request that needs to be updated. +function WAREHOUSE:_UnitDead(deadunit, request) + + -- Group the dead unit belongs to. + local group=deadunit:GetGroup() + + -- Check if this was the last unit of the group ==> whole group dead. + local isgroupdead=false + local nunits=0 + if group then + local nunits=group:GetSize() + -- One (or less) units in group. + if nunits<=1 then + isgroupdead=true + end + end + + local text=string.format("Unit %s died! Group %s: #units=%d, IsAlive=%s", deadunit:GetName(), group:GetName(), nunits, tostring(group:IsAlive())) + self:E(self.wid..text) + + local NoTriggerEvent=false + + if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then + + --- + -- Easy case: Group can simply be removed from the cargogroupset. + --- + + -- Remove dead group from carg group set. + if isgroupdead==true then + request.cargogroupset:Remove(group, NoTriggerEvent) + end + + else + + --- + -- Complicated case: Dead unit could be: + -- 1.) A Cargo unit (e.g. waiting to be picked up). + -- 2.) A Transport unit which itself holds cargo groups. + --- + + if self:_GroupIsTransport(group,request) then + + -- Get the carrier unit table holding the cargo groups inside this carrier. + local carrierunit=request.carriercargo[deadunit:GetName()] + + if carrierunit then + + -- Loop over all groups inside the carrier ==> all dead. + for _,cargogroup in pairs(carrierunit) do + + -- TODO: Check if remove really needs the name? Did I not always use the group object?! + --request.cargogroupset:Remove(ObjectName,NoTriggerEvent) + request.cargogroupset:Remove(cargogroup,NoTriggerEvent) + end + + end + + -- Whole carrier group is dead. Remove it from the carrier group set. + if isgroupdead then + request.transportcargoset:Remove(group, NoTriggerEvent) + end + + else + + -- This must have been an alive cargo group that was killed outside the carrier, e.g. waiting to be transported or waiting to be put back. + -- Remove dead group from cargo group set. + if isgroupdead==true then + + request.cargogroupset:Remove(group, NoTriggerEvent) + + -- TODO: This as well? + --request.transportcargoset:RemoveCargosByName(RemoveCargoNames) + end + + end + end +end + +--- Count alive and filter dead groups. +-- @param #WAREHOUSE self +-- @param Core.Set#SET_GROUP groupset Set of groups. Dead groups are removed from the set. +-- @return #number Number of alive groups. Returns zero if groupset is nil. +function WAREHOUSE:_FilterDead(groupset) + + local nalive=0 + + if groupset then + + -- Check if groups are still alive + local dead={} + for _,group in pairs(groupset:GetSetObjects()) do + if group and group:IsAlive() then + nalive=nalive+1 + else + table.insert(dead, group) + end + end + + -- TODO: Since the cargo groups are de- and re-spawned, does the counting for cargo groups actually work, when they are transported? + + -- Debug info. + self:T(self.wid..string.format("FilterDead: Alive=%d, Dead=%d", nalive, #dead)) + + -- Remove dead groups + local NoTriggerEvent=false + for _,group in pairs(dead) do + groupset:Remove(group, NoTriggerEvent) + end + end + + return nalive +end + + --- Warehouse event handling function. -- Handles the case when the airbase associated with the warehous is captured. -- @param #WAREHOUSE self @@ -5674,7 +5797,6 @@ function WAREHOUSE:_DisplayStatus() local text=string.format("\n------------------------------------------------------\n") text=text..string.format("Warehouse %s status: %s\n", self.alias, self:GetState()) text=text..string.format("------------------------------------------------------\n") - --text=text..string.format("Current status = %s\n", ) text=text..string.format("Coalition side = %d\n", self.coalition) text=text..string.format("Country name = %d\n", self.country) text=text..string.format("Airbase name = %s\n", airbasename)