From 498ec4f86bc5f9c8dd16794b4d5fc3c19079b603 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 9 Aug 2018 00:56:25 +0200 Subject: [PATCH 01/73] WAREHOUSE etc --- Moose Development/Moose/Core/Point.lua | 15 +++-- Moose Development/Moose/Core/Set.lua | 18 ++++-- Moose Development/Moose/Core/Zone.lua | 3 +- .../Moose/Functional/Warehouse.lua | 61 ++++++++++++++++--- .../Moose/Wrapper/Controllable.lua | 55 +++++++++++++++++ 5 files changed, 133 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8c456137d..55ba4ffda 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1205,13 +1205,18 @@ do -- COORDINATE return self:GetClosestParkingSpot(airbase, terminaltype, false) end - --- Gets the nearest coordinate to a road. + --- Gets the nearest coordinate to a road (or railroad). -- @param #COORDINATE self + -- @param #boolean Railroad (Optional) If true, closest point to railroad is returned rather than closest point to conventional road. Default false. -- @return #COORDINATE Coordinate of the nearest road. - function COORDINATE:GetClosestPointToRoad() - local x,y = land.getClosestPointOnRoads("roads", self.x, self.z) - local vec2={ x = x, y = y } - return COORDINATE:NewFromVec2(vec2) + function COORDINATE:GetClosestPointToRoad(Railroad) + local roadtype="roads" + if Railroad==true then + roadtype="railroads" + end + local x,y = land.getClosestPointOnRoads(roadtype, self.x, self.z) + local vec2={ x = x, y = y } + return COORDINATE:NewFromVec2(vec2) end diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 12a611250..6a284ab73 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4735,10 +4735,20 @@ function SET_ZONE:New() return self end +--- Add ZONE to SET_ZONE. +-- @param Core.Set#SET_ZONE self +-- @param Core.Zone#ZONE Zone Zone to add to the set. +-- @return #SET_ZONE self +function SET_ZONE:AddZone(Zone) + self:Add(Zone:GetName(), Zone) + return self +end + + --- Add ZONEs to SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param #string AddZoneNames A single name or an array of ZONE_BASE names. --- @return self +-- @return #SET_ZONE self function SET_ZONE:AddZonesByName( AddZoneNames ) local AddZoneNamesArray = ( type( AddZoneNames ) == "table" ) and AddZoneNames or { AddZoneNames } @@ -4753,7 +4763,7 @@ end --- Remove ZONEs from SET_ZONE. -- @param Core.Set#SET_ZONE self -- @param Core.Zone#ZONE_BASE RemoveZoneNames A single name or an array of ZONE_BASE names. --- @return self +-- @return #SET_ZONE self function SET_ZONE:RemoveZonesByName( RemoveZoneNames ) local RemoveZoneNamesArray = ( type( RemoveZoneNames ) == "table" ) and RemoveZoneNames or { RemoveZoneNames } @@ -4779,8 +4789,7 @@ end --- Get a random zone from the set. -- @param #SET_ZONE self --- @return Core.Zone#ZONE_BASE The random Zone. --- @return #nil if no zone in the collection. +-- @return Core.Zone#ZONE_BASE The random Zone or #nil if no zone in the collection. function SET_ZONE:GetRandomZone() if self:Count() ~= 0 then @@ -4806,6 +4815,7 @@ end --- Set a zone probability. -- @param #SET_ZONE self -- @param #string ZoneName The name of the zone. +-- @param #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. function SET_ZONE:SetZoneProbability( ZoneName, ZoneProbability ) local Zone = self:FindZone( ZoneName ) Zone:SetZoneProbability( ZoneProbability ) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 82c7a0be7..d0e368d3a 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -317,7 +317,8 @@ end --- Set the randomization probability of a zone to be selected. -- @param #ZONE_BASE self --- @param ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @param #number ZoneProbability A value between 0 and 1. 0 = 0% and 1 = 100% probability. +-- @return #ZONE_BASE self function ZONE_BASE:SetZoneProbability( ZoneProbability ) self:F( { self:GetName(), ZoneProbability = ZoneProbability } ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cf134349b..54c75cc50 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -117,8 +117,10 @@ WAREHOUSE.Descriptor = { ATTRIBUTE="attribute", } ---- Warehouse unit categories. These are used for +--- Warehouse generalited categories. -- @type WAREHOUSE.Attribute +-- @field #string TRANSPORT_PLANE Airplane with transport capability. Usually bigger. +-- @field #string TRANSPORT_HELO Helicopter with transport capability. WAREHOUSE.Attribute = { TRANSPORT_PLANE="Transport_Plane", TRANSPORT_HELO="Transport_Helo", @@ -132,6 +134,7 @@ WAREHOUSE.Attribute = { BOMBER="Bomber", TANK="Tank", TRUCK="Truck", + TRAIN="Train", SHIP="Ship", OTHER="Other", } @@ -504,23 +507,30 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Set a marker for the spawned group. spawncoord:MarkToAll(string.format("Spawnpoint %s",_alias)) + + local _attribute=_assetitem.attribute if _assetitem.category==Group.Category.GROUND then -- Spawn ground troops. _group=_spawn:SpawnFromCoordinate(spawncoord) - env.info(string.format("FF spawning group %s", _alias)) + 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 + if _attribute==WAREHOUSE.Attribute.FIGHTER then _terminal=AIRBASE.TerminalType.FighterAircraft - elseif _assetitem.attribute==WAREHOUSE.Attribute.BOMBER then + elseif _attribute==WAREHOUSE.Attribute.BOMBER or _attribute==WAREHOUSE.Attribute.TRANSPORT_PLANE or _attribute==WAREHOUSE.Attribute.TANKER or _attribute==WAREHOUSE.Attribute.AWACS then _terminal=AIRBASE.TerminalType.OpenBig + elseif _attribute==WAREHOUSE.Attribute.TRANSPORT_HELO or _attribute==WAREHOUSE.Attribute.ATTACKHELICOPTER then + _terminal=AIRBASE.TerminalType.HelicopterUsable end _group=_spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil,_terminal, true) elseif _assetitem.category==Group.Category.TRAIN then - + local _railroad=self.coordinate:GetClosestPointToRoad(true) + if _railroad then + _group=_spawn:SpawnFromCoordinate(_railroad) + end end if _group then @@ -561,7 +571,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif _cargocategory==Group.Category.SHIP then elseif _cargocategory==Group.Category.TRAIN then - + self:_RouteTrain(group, ToCoordinate) end end @@ -581,8 +591,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- 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 DeployZoneSet = SET_ZONE:New():FilterPrefixes("Deploy"):FilterStart() + --local DeployZoneSet = SET_ZONE:New():AddZonesByName(Request.airbase:GetZone():GetName()) + local DeployZoneSet = SET_ZONE:New():AddZone(Request.airbase:GetZone()) + local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER -- Filter the requested transport assets. @@ -651,6 +663,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) TransportSet:AddGroup(spawngroup) table.insert(_delid,_assetitem.id) + else + env.info("FF error spawngroup helo transport does not exist!") end end @@ -918,6 +932,32 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) end end +--- Route trains to their destination - or at least to the closest point on rail of the desired final destination. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP Group The train group. +-- @param Core.Point#COORDINATE Coordinate of the destination. Tail will be routed to the closest point +-- @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:_RouteTrain(Group, Coordinate, Speed) + + if Group and Group:IsAlive() then + + local _speed=Speed or Group:GetSpeedMax()*0.6 + + -- Create a + local Waypoints = Group:TaskGroundOnRailRoads(Coordinate, Speed) + + -- 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 + --- 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}. @@ -997,7 +1037,8 @@ function WAREHOUSE:_GetAttribute(groupname) 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") + local truck=group:HasAttribute("Trucks") and not group:GetCategory()==Group.Category.TRAIN + local train=group:GetCategory()==Group.Category.TRAIN -- Debug output. --[[ @@ -1039,6 +1080,8 @@ function WAREHOUSE:_GetAttribute(groupname) attribute=WAREHOUSE.Attribute.TANK elseif truck then attribute=WAREHOUSE.Attribute.TRUCK + elseif train then + attribute=WAREHOUSE.Attribute.TRAIN else attribute=WAREHOUSE.Attribute.OTHER end diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 84c25a1f4..767845e21 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1996,6 +1996,28 @@ do -- Route methods return self end + + --- Make the TRAIN Controllable to drive towards a specific point using railroads. + -- @param #CONTROLLABLE self + -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. + -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. + -- @param #number DelaySeconds (Optional) Wait for the specified seconds before executing the Route. Default is one second. + -- @return #CONTROLLABLE The CONTROLLABLE. + function CONTROLLABLE:RouteGroundOnRailRoads( ToCoordinate, Speed, DelaySeconds) + + -- Defaults. + Speed=Speed or 20 + DelaySeconds=DelaySeconds or 1 + + -- Get the route task. + local route=self:TaskGroundOnRailRoads(ToCoordinate, Speed) + + -- Route controllable to destination. + self:Route( route, DelaySeconds ) + + return self + end + --- Make a task for a GROUND Controllable to drive towards a specific point using (mostly) roads. @@ -2077,7 +2099,40 @@ do -- Route methods return route end + --- Make a task for a TRAIN Controllable to drive towards a specific point using railroad. + -- @param #CONTROLLABLE self + -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. + -- @param #number Speed (Optional) Speed in km/h. The default speed is 20 km/h. + -- @return Task + function CONTROLLABLE:TaskGroundOnRailRoads(ToCoordinate, Speed) + self:F2({ToCoordinate=ToCoordinate, Speed=Speed}) + + -- Defaults. + Speed=Speed or 20 + -- Current coordinate. + local FromCoordinate = self:GetCoordinate() + + -- Get path and path length on railroad. + local PathOnRail, LengthOnRail=FromCoordinate:GetPathOnRoad(ToCoordinate, false, true) + + -- Debug info. + self:T(string.format("Length on railroad = %.3f km", LengthOnRail/1000)) + + -- Route, ground waypoints along road. + local route={} + + -- Check if a valid path on railroad could be found. + if PathOnRail then + + table.insert(route, PathOnRail[1]:WaypointGround(Speed, "On Railroad")) + table.insert(route, PathOnRail[2]:WaypointGround(Speed, "On Railroad")) + + end + + return route + end + --- Make the AIR Controllable fly towards a specific point. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. From a2aa482bc1899471107d66341e1a4f55836b6708 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 9 Aug 2018 16:23:35 +0200 Subject: [PATCH 02/73] WH --- Moose Development/Moose/Core/Spawn.lua | 9 +- .../Moose/Functional/Warehouse.lua | 170 +++++++++++++----- Moose Development/Moose/Wrapper/Airbase.lua | 5 +- 3 files changed, 140 insertions(+), 44 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index a9817109e..586ac8396 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1299,6 +1299,7 @@ end -- @param #number TakeoffAltitude (optional) The altitude above the ground. -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. -- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. +-- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! -- @return Wrapper.Group#GROUP that was spawned or nil when nothing was spawned. -- @usage -- Spawn_Plane = SPAWN:New( "Plane" ) @@ -1319,7 +1320,7 @@ end -- -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold, nil, AIRBASE.TerminalType.OpenBig ) -- -function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn ) -- R2.2, R2.4 +function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType, EmergencyAirSpawn, Parkingdata ) -- R2.2, R2.4 self:F( { self.SpawnTemplatePrefix, SpawnAirbase, Takeoff, TakeoffAltitude, TerminalType } ) -- Get position of airbase. @@ -1434,6 +1435,10 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT self:T(string.format("Group %s is spawned on farp/ship/runway %s.", self.SpawnTemplatePrefix, SpawnAirbase:GetName())) nfree=SpawnAirbase:GetFreeParkingSpotsNumber(termtype, true) spots=SpawnAirbase:GetFreeParkingSpotsTable(termtype, true) + elseif Parkingdata~=nil then + -- Parking data explicitly set by user as input parameter. + nfree=#Parkingdata + spots=Parkingdata else if ishelo then if termtype==nil then @@ -1511,7 +1516,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT PointVec3=spots[1].Coordinate else - -- If there is absolutely not spot ==> air start! + -- If there is absolutely no spot ==> air start! _notenough=true end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 54c75cc50..01d96b243 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -152,22 +152,24 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.0" +WAREHOUSE.version="0.1.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehuse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add event handlers. --- TODO: Add AI_APC --- TODO: Add AI_HELICOPTER +-- DONE: Add AI_CARGO_AIRPLANE +-- DONE: Add AI_CARGO_APC +-- DONE: Add AI_CARGO_HELICOPTER -- TODO: Write documentation. --- TODO: Put active groups into the warehouse. +-- TODO: Put active groups into the warehouse, e.g. when they were transported to this warehouse. -- TODO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. -- TODO: Handle cases with immobile units. --- TODO: Add queue. +-- DONE: Add queue. -- TODO: How to handle multiple units in a transport group? --- TODO: Switch to AI_XXX_DISPATCHER +-- DONE: Switch to AI_CARGO_XXX_DISPATCHER +-- TODO: Add phyical object, if destroyed asssets are gone. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -178,7 +180,7 @@ WAREHOUSE.version="0.1.0" -- @param Wrapper.Airbase#AIRBASE airbase The airbase at which the warehouse is constructed. -- @return #WAREHOUSE self function WAREHOUSE:NewAirbase(airbase) - BASE:E({airbase=airbase}) + BASE:E({airbase=airbase:GetName()}) -- Print version. env.info(string.format("Adding warehouse v%s for airbase %s", WAREHOUSE.version, airbase:GetName())) @@ -200,8 +202,6 @@ function WAREHOUSE:NewAirbase(airbase) -- 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") @@ -275,6 +275,10 @@ end -- @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)) + + -- Debug mark spawn zone. + self.spawnzone:BoundZone(60,country.id.GERMANY) + self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.homebase:GetName()) -- handle events -- event takeoff @@ -326,7 +330,6 @@ function WAREHOUSE:onafterStatus(From, Event, To) -- 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 @@ -390,15 +393,12 @@ function WAREHOUSE:_CheckQueue() 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 @@ -449,7 +449,6 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) 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) @@ -466,6 +465,25 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) return true end +--- Get the proper terminal type based on generalized attribute of the group. +--@param #WAREHOUSE self +--@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit. +function WAREHOUSE:_GetTerminal(_attribute) + + local _terminal=AIRBASE.TerminalType.OpenBig + if _attribute==WAREHOUSE.Attribute.FIGHTER then + -- Fighter ==> small. + _terminal=AIRBASE.TerminalType.FighterAircraft + elseif _attribute==WAREHOUSE.Attribute.BOMBER or _attribute==WAREHOUSE.Attribute.TRANSPORT_PLANE or _attribute==WAREHOUSE.Attribute.TANKER or _attribute==WAREHOUSE.Attribute.AWACS then + -- Bigger aircraft. + _terminal=AIRBASE.TerminalType.OpenBig + elseif _attribute==WAREHOUSE.Attribute.TRANSPORT_HELO or _attribute==WAREHOUSE.Attribute.ATTACKHELICOPTER then + -- Helicopter. + _terminal=AIRBASE.TerminalType.HelicopterUsable + end + +end + --- On after "Request" event. Initiates the transport of the assets to the requesting airbase. -- @param #WAREHOUSE self -- @param #string From From state. @@ -475,7 +493,9 @@ end 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))) - ---------------------------------------------------------------- + ------------------------------------------------------------------------------------------------------------------------------------ + -- Cargo assets. + ------------------------------------------------------------------------------------------------------------------------------------ -- New empty cargo set in case we need it. local CargoGroups = SET_CARGO:New() @@ -487,11 +507,56 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Filter the requested assets. local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval) + -- General type and category (GROUND,...) + local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute + local _cargocategory=_assetstock[1].category --DCS#Group.Category + + --_cargotype. + -- Spawn the assets. local _delid={} local _spawngroups={} local _cargotype local _cargocategory + + + -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + + if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + + -- Count total number of units that will be spawned. + local ntotal=0 + for i=1,Request.ntransport do + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + local group=GROUP:FindByName(_assetitem.templatename) + ntotal=ntotal + #group:GetUnits() + end + + -- All spots are based on the same template group which must not be the case. + -- But I cant think of a better way to fetch all parking spots in advance. + local group=GROUP:FindByName(_assetstock[1].templatename) + local terminaltype=self:_GetTerminal(_assetstock[1].attribute) + local allspots=self.homebase:FindFreeParkingSpotForAircraft(group, terminaltype, 50, true, true, false, false, ntotal) + + env.info("FF allspots = "..#allspots) + env.info("FF notal = "..ntotal) + + -- No rearrange them again for each asset + local k=1 + for i=1,Request.ntransport do + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + local spots={} + local nunits=#GROUP:FindByName(_assetitem.templatename):GetUnits() + for j=1,nunits do + table.insert(spots,allspots[k]) + k=k+1 + end + table.insert(Parking, spots) + end + end + + -- Loop over cargo requests. for i=1,Request.nasset do -- Get stock item. @@ -503,6 +568,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- 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. @@ -511,39 +577,39 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _attribute=_assetitem.attribute 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 _attribute==WAREHOUSE.Attribute.FIGHTER then - _terminal=AIRBASE.TerminalType.FighterAircraft - elseif _attribute==WAREHOUSE.Attribute.BOMBER or _attribute==WAREHOUSE.Attribute.TRANSPORT_PLANE or _attribute==WAREHOUSE.Attribute.TANKER or _attribute==WAREHOUSE.Attribute.AWACS then - _terminal=AIRBASE.TerminalType.OpenBig - elseif _attribute==WAREHOUSE.Attribute.TRANSPORT_HELO or _attribute==WAREHOUSE.Attribute.ATTACKHELICOPTER then - _terminal=AIRBASE.TerminalType.HelicopterUsable - end - _group=_spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil,_terminal, true) + + _group=_spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, true, Parking[i]) + elseif _assetitem.category==Group.Category.TRAIN then + + -- Spawn train. local _railroad=self.coordinate:GetClosestPointToRoad(true) if _railroad then _group=_spawn:SpawnFromCoordinate(_railroad) end + end if _group then _spawngroups[i]=_group - _cargotype=_assetitem.attribute - _cargocategory=_assetitem.category table.insert(_delid,_assetitem.id) + -- Add groups to cargo. if Request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then local cargogroup = CARGO_GROUP:New(_group, _alias, _alias, _loadradius, _nearradius) CargoGroups:AddCargo(cargogroup) end - + else + self:E(self.wid.."ERROR: cargo asset could not be spawned!") end end @@ -552,7 +618,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:_DeleteStockItem(_id) end - ---------------------------------------------------------------- + ------------------------------------------------------------------------------------------------------------------------------------ + -- Self propelled assets. + ------------------------------------------------------------------------------------------------------------------------------------ -- No transport unit requested. Assets go by themselfes. if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -562,6 +630,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local group=_spawngroup --Wrapper.Group#GROUP local ToCoordinate=Request.airbase:GetZone():GetRandomCoordinate() + -- Route cargo to their destination. if _cargocategory==Group.Category.GROUND then self:_RouteGround(group, ToCoordinate) elseif _cargocategory==Group.Category.AIRPLANE then @@ -569,7 +638,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif _cargocategory==Group.Category.HELICOPTER then self:_RouteAir(group, Request.airbase) elseif _cargocategory==Group.Category.SHIP then - + self:E("ERROR: self propelled ship not implemented yet!") elseif _cargocategory==Group.Category.TRAIN then self:_RouteTrain(group, ToCoordinate) end @@ -582,19 +651,22 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- No cargo transport necessary. return end + + ------------------------------------------------------------------------------------------------------------------------------------ + -- Transport assets and dispachers + ------------------------------------------------------------------------------------------------------------------------------------ env.info("FF cargo set name(s) = "..CargoGroups:GetObjectNames()) ---------------------------------------------------------------- - local TransportSet = SET_GROUP:New() --:AddGroupsByName(Plane:GetName()) + local TransportSet = SET_GROUP:New() - -- Pickup and depoly locations. + -- Pickup and deploy zones/bases. 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 DeployZoneSet = SET_ZONE:New():AddZonesByName(Request.airbase:GetZone():GetName()) local DeployZoneSet = SET_ZONE:New():AddZone(Request.airbase:GetZone()) + -- Cargo dispatcher. local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER -- Filter the requested transport assets. @@ -602,6 +674,15 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Dependent on transport type, spawn the transports and set up the dispatchers. if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + + -- Now we try to find all parking spots for all transport groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + for i=1,Request.ntransport do + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + local group=GROUP:FindByName(_assetitem.templatename) + local spots=self.homebase:FindFreeParkingSpotForAircraft(group, AIRBASE.TerminalType.OpenBig) + table.insert(Parking, spots) + end -- Spawn the transport groups. local _delid={} @@ -609,14 +690,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + local _parking=Parking[i] -- 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) + local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -638,6 +719,16 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + + -- Now we try to find all parking spots for all transport groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + -- Note that not all assest need to be of the same type. Therefore, do + local Parking={} + for i=1,Request.ntransport do + local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem + local group=GROUP:FindByName(_assetitem.templatename) + local spots=self.homebase:FindFreeParkingSpotForAircraft(group, AIRBASE.TerminalType.HelicopterUsable) + table.insert(Parking, spots) + end -- Spawn the transport groups. local _delid={} @@ -645,15 +736,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem - + local _parking=Parking[i] + -- 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. + -- Spawn helo at airport. 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) + local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitUnControlled(false):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) if spawngroup then -- Set state of warehouse so we can retrieve it later. diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 70169576e..1af9548c2 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -594,8 +594,9 @@ end -- @param #boolean scanscenery (Optional) Scan for scenery as obstacles. Default false. Can cause problems with e.g. shelters. -- @param #boolean verysafe (Optional) If true, wait until an aircraft has taken off until the parking spot is considered to be free. Defaul false. -- @param #number nspots (Optional) Number of freeparking spots requested. Default is the number of aircraft in the group. +-- @param #table parkingdata (Optional) Parking spots data table. If not given it is automatically derived from the GetParkingSpotsTable() function. -- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. -function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots) +function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, scanunits, scanstatics, scanscenery, verysafe, nspots, parkingdata) -- Init default scanradius=scanradius or 50 @@ -647,7 +648,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, -- 1. A spot is considered as NOT free until an aircraft that is present has finally taken off. This might be a bit long especiall at smaller airports. -- 2. A "free" spot does not take the aircraft size into accound. So if two big aircraft are spawned on spots next to each other, they might overlap and get destroyed. -- 3. The routine return a free spot, if there a static objects placed on the spot. - local parkingdata=self:GetParkingSpotsTable(terminaltype) + parkingdata=parkingdata or self:GetParkingSpotsTable(terminaltype) -- Get the aircraft size, i.e. it's longest side of x,z. local aircraft=group:GetUnit(1) From 06688366c696cfa9a6a09188b811a0220697de81 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 9 Aug 2018 23:40:56 +0200 Subject: [PATCH 03/73] Warehouse 0.1.2 --- Moose Development/Moose/AI/AI_Cargo_APC.lua | 26 ++ .../Moose/AI/AI_Cargo_Dispatcher.lua | 14 +- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 2 +- .../Moose/AI/AI_Cargo_Helicopter.lua | 115 ++++++- Moose Development/Moose/Core/Zone.lua | 4 +- .../Moose/Functional/Warehouse.lua | 315 +++++++++++------- 6 files changed, 334 insertions(+), 142 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index 13f88324e..d231dd881 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -106,6 +106,7 @@ function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) self:AddTransition( "*", "Follow", "Following" ) self:AddTransition( "*", "Guard", "Unloaded" ) self:AddTransition( "*", "Home", "*" ) + self:AddTransition( "*", "BackHome" , "*" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) @@ -725,9 +726,34 @@ function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed ) self:F({Waypoints = Waypoints}) local Waypoint = Waypoints[#Waypoints] + + -- Task function triggering the arrived event. + local TaskFunction = APC:TaskFunction("AI_CARGO_APC._BackHome", self) + + -- Put task function on last waypoint. + APC:SetTaskWaypoint( Waypoint, TaskFunction ) APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. end end + +--- Function called when transport is back home and nothing more to do. Triggering the event BackHome. +-- @param Wrapper.Group#GROUP APC Cargo carrier. +-- @param #AI_CARGO_APC self +function AI_CARGO_APC._BackHome(APC, self) + --Trigger BackHome event. + APC:SmokeGreen() + self:__BackHome(1) +end + +--- On after BackHome event. +-- @param #AI_CARGO_APC self +-- @param Wrapper.Group#GROUP APC +-- @param From +-- @param Event +-- @param To +function AI_CARGO_APC:onafterBackHome( APC, From, Event, To ) + APC:SmokeRed() +end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 7ac009283..cfb2da656 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -144,8 +144,8 @@ 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_GROUP SetCarriers +-- @param Core.Set#SET_CARGO SetCargos -- @param Core.Set#SET_ZONE DeployZonesSet -- @return #AI_CARGO_DISPATCHER -- @usage @@ -454,6 +454,11 @@ function AI_CARGO_DISPATCHER:onafterMonitor() self.CarrierHome[Carrier] = true AI_Cargo:__Home( 60, self.HomeZone:GetRandomPointVec2() ) end + elseif self.HomeBase then + if not self.CarrierHome[Carrier] then + self.CarrierHome[Carrier] = true + AI_Cargo:__RTB( 60, self.HomeBase ) + end end end end @@ -548,11 +553,12 @@ end -- @param Cargo.Cargo#CARGO Cargo -- @return #AI_CARGO_DISPATCHER function AI_CARGO_DISPATCHER:OnAfterLoaded( From, Event, To, Carrier, Cargo ) + if self.DeployZonesSet then - + 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 ) ) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index 413ca7c26..69947e082 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:NewWithZones( 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_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 1e3141649..846289e03 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -53,7 +53,9 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( "*", "Landed", "*" ) self:AddTransition( "*", "Queue", "*" ) self:AddTransition( "*", "Orbit" , "*" ) - self:AddTransition( "*", "Home" , "*" ) + self:AddTransition( "*", "Home" , "*" ) + self:AddTransition( "*", "RTB" , "*" ) + self:AddTransition( "*", "BackHome" , "*" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) @@ -714,7 +716,7 @@ end -- @param Event -- @param To -- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. +-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 80% of max possible speed the unit can go. function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed ) if Helicopter and Helicopter:IsAlive() ~= nil then @@ -725,9 +727,9 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Calculate the target route point. - Coordinate.y = math.random( 50, 200 ) + Coordinate.y = math.random( 100, 500 ) - local _speed=Speed or Helicopter:GetSpeedMax()*0.5 + local _speed=Speed or Helicopter:GetSpeedMax()*0.8 --- Create a route point of type air. local CoordinateFrom = Helicopter:GetCoordinate() @@ -756,11 +758,14 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat Helicopter:WayPointInitialize( Route ) local Tasks = {} - + Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) + Tasks[#Tasks+1] = Helicopter:TaskFunction("AI_CARGO_HELICOPTER._BackHome", self) + Route[#Route].task = Helicopter:TaskCombo( Tasks ) Route[#Route+1] = WaypointTo + -- Now route the helicopter Helicopter:Route( Route, 0 ) @@ -769,3 +774,103 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat end + +--- On after RTB event. Route the helicopter from one airport or it's current position to another airbase. +-- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter Cargo helicopter. +-- @param From +-- @param Event +-- @param To +-- @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. +function AI_CARGO_HELICOPTER:onafterRTB( Helicopter, From, Event, To, Airbase, Speed) + + if Helicopter and Helicopter:IsAlive() then + + -- Set takeoff type. + local Takeoff = SPAWN.Takeoff.Hot + + -- Get template of group. + local Template = Helicopter:GetTemplate() + + -- Nil check + if Template==nil then + return + 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 Helicopter:GetSpeedMax()*0.8 + ) + ToWaypoint["airdromeId"] = Airbase:GetID() + ToWaypoint["speed_locked"] = true + + -- Task function triggering the arrived event. + local TaskFunction = Helicopter:TaskFunction("AI_CARGO_HELICOPTER._BackHome", self) + + -- Put task function on last waypoint. + Helicopter:SetTaskWaypoint( ToWaypoint, TaskFunction ) + + + -- 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. + Helicopter:RespawnAtCurrentAirbase(Template, Takeoff, false) + + else + + -- From point. + local GroupPoint = Helicopter:GetVec2() + local FromWaypoint = {} + FromWaypoint.x = GroupPoint.x + FromWaypoint.y = GroupPoint.y + FromWaypoint.type = "Turning Point" + FromWaypoint.action = "Turning Point" + FromWaypoint.speed = Helicopter:GetSpeedMax()*0.8 + + -- The two route points. + Points[1] = FromWaypoint + Points[2] = ToWaypoint + + local PointVec3 = Helicopter:GetPointVec3() + Template.x = PointVec3.x + Template.y = PointVec3.z + + Template.route.points = Points + + local GroupSpawned = Helicopter:Respawn(Template) + + end + end +end + +--- Function called when transport is back home and nothing more to do. Triggering the event BackHome. +-- @param Wrapper.Group#GROUP Helicopter Cargo helicopter. +-- @param #AI_CARGO_HELICOPTER self +function AI_CARGO_HELICOPTER._BackHome(Group, self) + --Trigger BackHome event. + Group:SmokeRed() + self:__BackHome(1) +end + + +--- On after BackHome event. +-- @param #AI_CARGO_HELICOPTER self +-- @param Wrapper.Group#GROUP Helicopter Cargo helo. +-- @param From +-- @param Event +-- @param To +function AI_CARGO_HELICOPTER:onafterBackHome( Helicopter, From, Event, To ) + Helicopter:SmokeRed() +end \ No newline at end of file diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index d0e368d3a..c417ad6a6 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -312,7 +312,6 @@ end -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) - end --- Set the randomization probability of a zone to be selected. @@ -330,8 +329,7 @@ end -- @param #ZONE_BASE self -- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:GetZoneProbability() - self:F2() - + self:F2() return self.ZoneProbability end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 01d96b243..83c703062 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -152,7 +152,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.1" +WAREHOUSE.version="0.1.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehuse todo list. @@ -379,6 +379,83 @@ function WAREHOUSE:AddRequest(airbase, AssetDescriptor, AssetDescriptorValue, nA table.insert(self.queue, request) end +---Checks if the request can be fullfilled. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem qitem The request to be checked. +-- @return #boolean If true, request can be executed. If false, something is not right. +function WAREHOUSE:_CheckRequest(request) + + local okay=true + + -- Check if number of requested assets is in stock. + local _assets=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) + + -- Get the attibute of the requested asset. + local _assetattribute=_assets[1].attribute + local _assetcategory=_assets[1].category + + -- Check if enough assets are in stock. + if request.nasset > #_assets then + local text=string.format("Request denied! Not enough assets currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check available parking for asset units. + local Parkingdata=self.homebase:GetParkingSpotsTable() + env.info("FF number parking data before "..#Parkingdata) + local Parking + if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then + Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) + env.info("FF number parking data after assets "..#Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + + -- Check that a transport units. + if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then + + -- Transports in stock. + local _transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) + + -- Get the attibute of the transport units. + local _transportattribute=_transports[1].attribute + local _transportcategory=_transports[1].category + + -- Check if enough transport units are available. + if request.ntransport > #_transports then + local text=string.format("Request denied! Not enough transport units currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check available parking for transport units. + if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then + Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) + env.info("FF number parking data after transport "..#Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + else + -- self propelled case. + + end + + return okay +end + ---Sorts the queue and checks if the request can be fullfilled. -- @param #WAREHOUSE self -- @return #WAREHOUSE.Queueitem Chosen request. @@ -387,28 +464,11 @@ 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) - if qitem.nasset > _instock then - 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 - 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) + local okay=self:_CheckRequest(qitem) if okay==true then request=qitem break @@ -428,61 +488,26 @@ end -- @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))) + env.info(self.wid..string.format("Airbase %s requesting %d assets of %s=%s by transport %s", + Request.airbase:GetName(), Request.nasset, tostring(Request.assetdesc), tostring(Request.assetdescval), tostring(Request.transporttype))) -- 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) + local _assets=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) -- 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) + if #_assets < Request.nasset then + local text=string.format("Request denied! Not enough assets currently in stock. Requested %d < %d in stock.", Request.nasset, #_assets) 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 ---- Get the proper terminal type based on generalized attribute of the group. ---@param #WAREHOUSE self ---@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit. -function WAREHOUSE:_GetTerminal(_attribute) - - local _terminal=AIRBASE.TerminalType.OpenBig - if _attribute==WAREHOUSE.Attribute.FIGHTER then - -- Fighter ==> small. - _terminal=AIRBASE.TerminalType.FighterAircraft - elseif _attribute==WAREHOUSE.Attribute.BOMBER or _attribute==WAREHOUSE.Attribute.TRANSPORT_PLANE or _attribute==WAREHOUSE.Attribute.TANKER or _attribute==WAREHOUSE.Attribute.AWACS then - -- Bigger aircraft. - _terminal=AIRBASE.TerminalType.OpenBig - elseif _attribute==WAREHOUSE.Attribute.TRANSPORT_HELO or _attribute==WAREHOUSE.Attribute.ATTACKHELICOPTER then - -- Helicopter. - _terminal=AIRBASE.TerminalType.HelicopterUsable - end - -end --- On after "Request" event. Initiates the transport of the assets to the requesting airbase. -- @param #WAREHOUSE self @@ -504,57 +529,22 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _loadradius=5000 local _nearradius=35 - -- Filter the requested assets. - local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval) + -- Filter the requested cargo assets. + local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) - -- General type and category (GROUND,...) + -- General type and category. local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute local _cargocategory=_assetstock[1].category --DCS#Group.Category - --_cargotype. + -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + Parking=self:_GetParkingForAssets(_assetstock) + end -- Spawn the assets. local _delid={} local _spawngroups={} - local _cargotype - local _cargocategory - - - -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - local Parking={} - - if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - - -- Count total number of units that will be spawned. - local ntotal=0 - for i=1,Request.ntransport do - local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem - local group=GROUP:FindByName(_assetitem.templatename) - ntotal=ntotal + #group:GetUnits() - end - - -- All spots are based on the same template group which must not be the case. - -- But I cant think of a better way to fetch all parking spots in advance. - local group=GROUP:FindByName(_assetstock[1].templatename) - local terminaltype=self:_GetTerminal(_assetstock[1].attribute) - local allspots=self.homebase:FindFreeParkingSpotForAircraft(group, terminaltype, 50, true, true, false, false, ntotal) - - env.info("FF allspots = "..#allspots) - env.info("FF notal = "..ntotal) - - -- No rearrange them again for each asset - local k=1 - for i=1,Request.ntransport do - local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem - local spots={} - local nunits=#GROUP:FindByName(_assetitem.templatename):GetUnits() - for j=1,nunits do - table.insert(spots,allspots[k]) - k=k+1 - end - table.insert(Parking, spots) - end - end -- Loop over cargo requests. for i=1,Request.nasset do @@ -670,20 +660,24 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) 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) + local _assetstock=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, Request.transporttype, Request.ntransport) + + -- General type and category. + local _transporttype=_assetstock[1].attribute --#WAREHOUSE.Attribute + local _transportcategory=_assetstock[1].category --DCS#Group.Category + + -- Now we try to find all parking spots for all transport groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + + -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then + Parking=self:_GetParkingForAssets(_assetstock) + end -- Dependent on transport type, spawn the transports and set up the dispatchers. if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - -- Now we try to find all parking spots for all transport groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - local Parking={} - for i=1,Request.ntransport do - local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem - local group=GROUP:FindByName(_assetitem.templatename) - local spots=self.homebase:FindFreeParkingSpotForAircraft(group, AIRBASE.TerminalType.OpenBig) - table.insert(Parking, spots) - end - -- Spawn the transport groups. local _delid={} for i=1,Request.ntransport do @@ -719,16 +713,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - - -- Now we try to find all parking spots for all transport groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - -- Note that not all assest need to be of the same type. Therefore, do - local Parking={} - for i=1,Request.ntransport do - local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem - local group=GROUP:FindByName(_assetitem.templatename) - local spots=self.homebase:FindFreeParkingSpotForAircraft(group, AIRBASE.TerminalType.HelicopterUsable) - table.insert(Parking, spots) - end -- Spawn the transport groups. local _delid={} @@ -754,7 +738,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) table.insert(_delid,_assetitem.id) else - env.info("FF error spawngroup helo transport does not exist!") + self:E(self.wid.."ERROR: spawngroup helo transport does not exist!") end end @@ -767,7 +751,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportSet, CargoGroups, DeployZoneSet) -- Home zone. - CargoTransport:SetHomeZone(self.spawnzone) + CargoTransport:SetHomeBase(self.homebase) + --CargoTransport:SetHomeZone(self.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.APC then @@ -803,6 +788,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Define dispatcher for this task. CargoTransport = AI_CARGO_DISPATCHER_APC:NewWithZones(TransportSet, CargoGroups, DeployZoneSet, 0) + -- Set home zone. + CargoTransport:SetHomeZone(self.spawnzone) + elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then self:E(self.wid.."ERROR: transport by train not supported yet!") @@ -1048,13 +1036,83 @@ function WAREHOUSE:_RouteTrain(Group, Coordinate, Speed) end end +--- Get the proper terminal type based on generalized attribute of the group. +--@param #WAREHOUSE self +--@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit. +function WAREHOUSE:_GetTerminal(_attribute) + + local _terminal=AIRBASE.TerminalType.OpenBig + if _attribute==WAREHOUSE.Attribute.FIGHTER then + -- Fighter ==> small. + _terminal=AIRBASE.TerminalType.FighterAircraft + elseif _attribute==WAREHOUSE.Attribute.BOMBER or _attribute==WAREHOUSE.Attribute.TRANSPORT_PLANE or _attribute==WAREHOUSE.Attribute.TANKER or _attribute==WAREHOUSE.Attribute.AWACS then + -- Bigger aircraft. + _terminal=AIRBASE.TerminalType.OpenBig + elseif _attribute==WAREHOUSE.Attribute.TRANSPORT_HELO or _attribute==WAREHOUSE.Attribute.ATTACKHELICOPTER then + -- Helicopter. + _terminal=AIRBASE.TerminalType.HelicopterUsable + end + +end + +--- Get parking data for all air assets that need to be spawned at an airbase. +--@param #WAREHOUSE self +--@param #table assetlist A list of assets for which parking spots are required. +--@param #table parkingdata Table of the complete parking data to check. Default is to take it from the @{Wrapper.Airbase#AIRBASE.GetParkingSpotsTable}() function. +--@return #table A table with parking spots for each asset group. +--@return #table The reduced parking data table of the spots that have not been assigned. +function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) + + --- Remove selected spots from parking data table. + local function removeparking(parkingdata,spots) + for j=1,#spots do + for i=1,#parkingdata do + if parkingdata[i].TerminalID==spots[j].TerminalID then + table.remove(parkingdata,j) + break + end + end + end + end + + -- Get complete parking data of the airbase. + parkingdata=parkingdata or self.homebase:GetParkingSpotsTable() + + local assetparking={} + for i=1,#assetlist do + + -- Asset specifics. + local asset=assetlist[i] --#WAREHOUSE.Stockitem + local group=GROUP:FindByName(asset.templatename) + local nunits=#group:GetUnits() + local terminal=self:_GetTerminal(asset.attribute) + + -- Find appropiate parking spots for this group. + local spots=self.homebase:FindFreeParkingSpotForAircraft(group, terminal, nil, nil, nil, nil, nil, nil, parkingdata) + + -- Not enough parking spots for this group. + if #spots=nmax then + return filtered + end end end @@ -1221,8 +1282,6 @@ end -- @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 @@ -1230,8 +1289,6 @@ function WAREHOUSE:_DeleteQueueItem(_uid) break end end - env.info("FF AFTER delete queue") - self:_PrintQueue() end --- Sort requests queue wrt prio and request uid. From 195459d6d83dc2fe220bf4f0ee0de238746c3895 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 10 Aug 2018 16:26:39 +0200 Subject: [PATCH 04/73] Warehouse v0.1.3 --- .../Moose/AI/AI_Cargo_Dispatcher.lua | 2 + .../Moose/Functional/Warehouse.lua | 505 ++++++++++++------ 2 files changed, 331 insertions(+), 176 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index cfb2da656..bb39d63ce 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -123,6 +123,8 @@ function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo ) self:AddTransition( "*", "Unloaded", "*" ) self:AddTransition( "*", "Home", "*" ) + self:AddTransition( "*", "RTB", "*" ) --FF + self:AddTransition( "*", "BackHome", "*" ) --FF self.MonitorTimeInterval = 30 self.DeployRadiusInner = 200 diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 83c703062..49bd8d8aa 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -22,16 +22,19 @@ -- @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 DCS#coalition.side coalition Coalition ID the warehouse belongs to. +-- @field DCS#country.id country Country ID 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 uid Unit identifier of the warehouse. Derived from the associated airbase. -- @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}. +-- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. 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. @@ -74,12 +77,15 @@ WAREHOUSE = { Debug = false, Report = true, coalition = nil, + country = nil, homebase = nil, category = nil, coordinate = nil, spawnzone = nil, wid = nil, + uid = nil, markerid = nil, + warehouse = nil, assetid = 0, queueid = 0, stock = {}, @@ -90,6 +96,7 @@ WAREHOUSE = { -- @type WAREHOUSE.Stockitem -- @field #number id Unique id of the asset. -- @field #string templatename Name of the template group. +-- @field #table template The spawn template of the 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. @@ -147,12 +154,12 @@ WAREHOUSE.TransportType = { APC = "Transport_APC", SHIP = "Ship", TRAIN = "Train", - SELFPROPELLED = "Selfporpelled", + SELFPROPELLED = "Selfpropelled", } --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.2" +WAREHOUSE.version="0.1.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehuse todo list. @@ -195,8 +202,10 @@ function WAREHOUSE:NewAirbase(airbase) self.homebase=airbase self.coordinate=airbase:GetCoordinate() self.coalition=airbase:GetCoalition() + self.country=airbase:GetCountry() self.category=airbase:GetDesc().category - + self.uid=airbase:GetID() + -- Get the closest point on road. local _road=self.coordinate:GetClosestPointToRoad():GetVec2() @@ -255,29 +264,21 @@ function WAREHOUSE:NewAirbase(airbase) 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 +--- On after Start event. Starts the warehouse. Addes event handlers and schedules status updates of reqests and queue. -- @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)) + self:E(self.wid..string.format("Starting warehouse at airbase %s, category %d, coalition %d, country %d", self.homebase:GetName(), self.category, self.coalition, self.country)) -- Debug mark spawn zone. - self.spawnzone:BoundZone(60,country.id.GERMANY) + --self.spawnzone:BoundZone(60, country.id.GERMANY) + self.spawnzone:BoundZone(60, self.country) self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.homebase:GetName()) -- handle events @@ -291,7 +292,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Warehouse +--- On after Status event. Checks the queue and handles requests. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -339,147 +340,6 @@ 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 - ----Checks if the request can be fullfilled. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Queueitem qitem The request to be checked. --- @return #boolean If true, request can be executed. If false, something is not right. -function WAREHOUSE:_CheckRequest(request) - - local okay=true - - -- Check if number of requested assets is in stock. - local _assets=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) - - -- Get the attibute of the requested asset. - local _assetattribute=_assets[1].attribute - local _assetcategory=_assets[1].category - - -- Check if enough assets are in stock. - if request.nasset > #_assets then - local text=string.format("Request denied! Not enough assets currently available.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - - -- Check available parking for asset units. - local Parkingdata=self.homebase:GetParkingSpotsTable() - env.info("FF number parking data before "..#Parkingdata) - local Parking - if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then - Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) - env.info("FF number parking data after assets "..#Parkingdata) - if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - end - - - -- Check that a transport units. - if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then - - -- Transports in stock. - local _transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) - - -- Get the attibute of the transport units. - local _transportattribute=_transports[1].attribute - local _transportcategory=_transports[1].category - - -- Check if enough transport units are available. - if request.ntransport > #_transports then - local text=string.format("Request denied! Not enough transport units currently available.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - - -- Check available parking for transport units. - if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then - Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) - env.info("FF number parking data after transport "..#Parkingdata) - if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - end - - else - -- self propelled case. - - end - - return okay -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() - - -- 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=self:_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. @@ -556,8 +416,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) 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 _alias=string.format("%s_WID-%02d_AID-%04d_RID-%04d", _assetitem.unittype, self.uid,_assetitem.id,Request.uid) + local _alias=self:_Alias(_assetitem,Request) + --local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) + local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) local _group=nil --Wrapper.Group#GROUP @@ -636,6 +498,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end -- Delete request from queue. + table.insert(self.pending, Request) self:_DeleteQueueItem(Request.uid) -- No cargo transport necessary. @@ -687,11 +550,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _parking=Parking[i] -- Spawn with ALIAS here or DCS crashes! - local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + local _alias=self:_Alias(_assetitem, Request) -- Spawn plane at airport in uncontrolled state. local _takeoff=SPAWN.Takeoff.Cold - local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) + --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) + local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) + local spawngroup=spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -723,11 +589,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _parking=Parking[i] -- Spawn with ALIAS here or DCS crashes! - local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + local _alias=self:_Alias(_assetitem, Request) -- Spawn helo at airport. local _takeoff=SPAWN.Takeoff.Hot - local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitUnControlled(false):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) + --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) + local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) + local spawngroup=spawn:InitUnControlled(false):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -764,10 +633,13 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem -- Spawn with ALIAS here or DCS crashes! - local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) + local _alias=self:_Alias(_assetitem, Request) -- Spawn plane at airport in uncontrolled state. - local spawngroup=SPAWN:NewWithAlias(_assetitem.templatename,_alias):SpawnFromCoordinate(self.spawnzone:GetRandomCoordinate()) + local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) + local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) + local spawngroup=spawn:SpawnFromCoordinate(self.spawnzone:GetRandomCoordinate()) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -831,28 +703,46 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Trigger Delivered event. warehouse:__Delivered(1, group) end + + --- On after BackHome event. + function CargoTransport:OnAfterBackHome(From, Event, To, Carrier) + + -- Get warehouse state. + local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + Carrier:SmokeRed() + + -- Add carrier back to warehouse stock. Actual unit is destroyed. + warehouse:AddAsset(Carrier:GetName(), 1) + + end -- Start dispatcher. CargoTransport:__Start(5) -- Delete request from queue. + table.insert(self.pending, Request) self:_DeleteQueueItem(Request.uid) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Warehouse +--- On after "Delivered" event. -- @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") +-- @param Wrapper.Group#GROUP group The group that was delivered. +function WAREHOUSE:onafterDelivered(From, Event, To, group) + self:E(self.wid..string.format("Cargo %s delivered!", group:GetName())) + + -- Route a ground group to the closest point on road. + if group:IsGround() and group:GetSpeedMax()>0 then + local road=group:GetCoordinate():GetClosestPointToRoad() + local speed=group:GetSpeedMax()*0.6 + group:RouteGroundTo(road, speed, "Off Road") + end + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -894,11 +784,22 @@ function WAREHOUSE:AddAsset(templategroupname, ngroups) self.assetid=self.assetid+1 stockitem.id=self.assetid stockitem.templatename=templategroupname + stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) stockitem.category=DCScategory stockitem.unittype=DCStype stockitem.attribute=attribute + + -- Modify the template so that the group is spawned with the right coalition. + stockitem.template.CoalitionID=self.coalition + stockitem.template.CountryID=self.country + table.insert(self.stock, stockitem) end + + -- Destroy group if it is alive. + if group:IsAlive()==true then + group:Destroy() + end else -- Group name does not exist! @@ -908,10 +809,262 @@ function WAREHOUSE:AddAsset(templategroupname, ngroups) 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 + +--- Add a request for the warehouse. +-- @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 --DCS#Airbase.Category + + -- No airplanes from to to FARPS. + 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 + + local request_air=false + local request_plane=false + local request_helo=false + local request_ground=false + local request_naval=false + + -- Check if category is matching + if AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then + if AssetDescriptorValue==Group.Category.AIRPLANE then + request_plane=true + elseif AssetDescriptorValue==Group.Category.HELICOPTER then + request_helo=true + elseif AssetDescriptorValue==Group.Category.GROUND then + request_ground=true + elseif AssetDescriptorValue==Group.Category.SHIP then + request_naval=true + elseif AssetDescriptorValue==Group.Category.TRAIN then + request_ground=true + else + self:E("ERROR: incorrect request. Asset Descriptor missmatch! Has to be Group.Cagetory.AIRPLANE, ...") + return + end + end + + -- Check attribute is matching + if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then + if AssetDescriptorValue==WAREHOUSE.Attribute.ARTILLERY then + request_ground=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.ATTACKHELICOPTER then + request_helo=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.AWACS then + request_plane=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.BOMBER then + request_plane=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.FIGHTER then + request_plane=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.INFANTRY then + request_ground=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.OTHER then + self:E("ERROR: incorrect request. Asset attribute WAREHOUSE.Attribute.OTHER is not valid!") + return + elseif AssetDescriptorValue==WAREHOUSE.Attribute.SHIP then + request_naval=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TANK then + request_ground=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TANKER then + request_plane=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRAIN then + request_ground=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRANSPORT_APC then + request_ground=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRANSPORT_HELO then + request_helo=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRANSPORT_PLANE then + request_plane=true + elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRUCK then + request_ground=true + else + self:E("ERROR: incorrect request. Unknown asset attribute!") + return + end + end + + request_air=request_helo or request_plane + + if request_air then + + if request_plane then + -- No airplane to or from FARPS. + if request_category==Airbase.Category.HELIPAD or self.category==Airbase.Category.HELIPAD then + self:E("ERROR: incorrect request. Asset aircraft requestst but warehouse or requestor is HELIPAD/FARP!") + return + end + elseif request_helo then + + end + + elseif request_ground then + + -- No ground assets directly to ships. + if (request_category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) + and not (TransportType==WAREHOUSE.TransportType.AIRPLANE or TransportType==WAREHOUSE.TransportType.HELICOPTER) then + self:E("ERROR: incorrect request. Ground asset requested but warehouse or requestor is SHIP!") + return + end + + elseif request_naval then + + 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 + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Checks if the request can be fullfilled. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem qitem The request to be checked. +-- @return #boolean If true, request can be executed. If false, something is not right. +function WAREHOUSE:_CheckRequest(request) + + local okay=true + + -- Check if number of requested assets is in stock. + local _assets=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) + + -- Get the attibute of the requested asset. + local _assetattribute=_assets[1].attribute + local _assetcategory=_assets[1].category + + -- Check if enough assets are in stock. + if request.nasset > #_assets then + local text=string.format("Request denied! Not enough assets currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check available parking for asset units. + local Parkingdata=self.homebase:GetParkingSpotsTable() + local Parking + if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then + Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + + -- Check that a transport units. + if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then + + -- Transports in stock. + local _transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) + + -- Get the attibute of the transport units. + local _transportattribute=_transports[1].attribute + local _transportcategory=_transports[1].category + + -- Check if enough transport units are available. + if request.ntransport > #_transports then + local text=string.format("Request denied! Not enough transport units currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check available parking for transport units. + if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then + Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + else + -- self propelled case. + + end + + return okay +end + + +--- Creates a unique name for spawned assets. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem _assetitem Asset for which the name is created. +-- @param #WAREHOUSE.Queueitem _queueitem (Optional) Request specific name. +-- @return #string Alias name "UnitType\_WID-%02d\_AID-%04d" +function WAREHOUSE:_Alias(_assetitem,_queueitem) + local _alias=string.format("%s_WID-%02d_AID-%04d", _assetitem.unittype, self.uid,_assetitem.id) + if _queueitem then + _alias=_alias..string.format("_RID-%04d",_queueitem.uid) + end + return _alias +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() + + -- 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=self:_CheckRequest(qitem) + if okay==true then + request=qitem + break + end + end + + -- Execute request. + return request +end + --- Route ground units to destination. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP Group The ground group. @@ -997,9 +1150,9 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) -- Task function triggering the arrived event. local Task = Aircraft:TaskFunction("WAREHOUSE._Arrived", self) - -- or - --ToWaypoint.task=Aircraft:TaskCombo({Task}) - ToWaypoint.task={Task} + --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 From d91166c3c44cd079ab64a74a3c81b7c4398b2725 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 13 Aug 2018 00:36:34 +0200 Subject: [PATCH 05/73] Warehouse 0.1.4 zone: fixed GetRandomCoordinate inner, outer missing controllable: fixed path cannot be found group: added getrange unit: added getrange --- Moose Development/Moose/Core/Message.lua | 5 +- Moose Development/Moose/Core/Point.lua | 13 +- Moose Development/Moose/Core/Spawn.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 2 +- .../Moose/Functional/Warehouse.lua | 995 ++++++++++++------ .../Moose/Wrapper/Controllable.lua | 40 +- Moose Development/Moose/Wrapper/Group.lua | 34 +- Moose Development/Moose/Wrapper/Unit.lua | 22 + 8 files changed, 782 insertions(+), 331 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 5ccfc3b65..1369f47be 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -294,8 +294,9 @@ end --- Sends a MESSAGE to a Coalition if the given Condition is true. -- @param #MESSAGE self --- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. --- @return #MESSAGE +-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}. +-- @param #boolean Condition Sends the message only if the condition is true. +-- @return #MESSAGE self function MESSAGE:ToCoalitionIf( CoalitionSide, Condition ) self:F( CoalitionSide ) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 55ba4ffda..0006d597f 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1098,11 +1098,14 @@ do -- COORDINATE --- Gets the nearest airbase with respect to the current coordinates. -- @param #COORDINATE self - -- @param #number AirbaseCategory Category of the airbase. + -- @param #number Category (Optional) Category of the airbase. Enumerator of @{Wrapper.Airbase#AIRBASE.Category}. + -- @param #number Coalition (Optional) Coalition 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() + function COORDINATE:GetClosestAirbase(Category, Coalition) + + -- Get all airbases of the map. + local airbases=AIRBASE.GetAllAirbases(Coalition) local closest=nil local distmin=nil @@ -1110,7 +1113,7 @@ do -- COORDINATE 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 + if Category and Category==category or Category==nil then local dist=self:Get2DDistance(airbase:GetCoordinate()) if closest==nil then distmin=dist @@ -1257,6 +1260,8 @@ do -- COORDINATE Path[#Path+1]=COORDINATE:NewFromVec2(_vec2) --COORDINATE:NewFromVec2(_vec2):SmokeGreen() end + --COORDINATE:NewFromVec2(path[1]):SmokeBlue() + --COORDINATE:NewFromVec2(path[#path]):SmokeBlue() else self:E("Path is nil. No valid path on road could be found.") diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 586ac8396..bd2f4653c 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -496,7 +496,7 @@ end --- Sets the coalition of the spawned group. Note that it might be necessary to also set the country explicitly! -- @param #SPAWN self --- @param #DCS.coalition Coaliton Coaliton of the group as number of enumerator, i.e. 0=coaliton.side.NEUTRAL, 1=coaliton.side.RED, 2=coalition.side.BLUE. +-- @param DCS#coalition.side Coalition Coalition of the group as number of enumerator, i.e. 0=coaliton.side.NEUTRAL, 1=coaliton.side.RED, 2=coalition.side.BLUE. -- @return #SPAWN self function SPAWN:InitCoalition( Coalition ) self:F({coalition=Coalition}) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index c417ad6a6..207587aee 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -933,7 +933,7 @@ end function ZONE_RADIUS:GetRandomCoordinate( inner, outer ) self:F( self.ZoneName, inner, outer ) - local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2() ) + local Coordinate = COORDINATE:NewFromVec2( self:GetRandomVec2(inner, outer) ) self:T3( { Coordinate = Coordinate } ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 49bd8d8aa..00c3714c0 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -22,11 +22,14 @@ -- @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 Wrapper.Static#STATIC warehouse The phyical warehouse structure. -- @field DCS#coalition.side coalition Coalition ID the warehouse belongs to. -- @field DCS#country.id country Country ID the warehouse belongs to. --- @field Wrapper.Airbase#AIRBASE homebase Airbase the warehouse belongs to. +-- @field Wrapper.Airbase#AIRBASE airbase 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.Point#COORDINATE road Closest point to warehouse on road. +-- @field Core.Point#COORDINATE road Closest point to warehouse on rail. -- @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 uid Unit identifier of the warehouse. Derived from the associated airbase. @@ -76,43 +79,52 @@ WAREHOUSE = { ClassName = "WAREHOUSE", Debug = false, Report = true, + warehouse = nil, coalition = nil, country = nil, - homebase = nil, + airbase = nil, category = nil, coordinate = nil, + road = nil, + rail = nil, spawnzone = nil, wid = nil, uid = nil, markerid = nil, - warehouse = nil, assetid = 0, queueid = 0, stock = {}, queue = {}, + pending = {}, } --- Item of the warehouse stock table. -- @type WAREHOUSE.Stockitem --- @field #number id Unique id of the asset. +-- @field #number uid Unique id of the asset. -- @field #string templatename Name of the template group. -- @field #table template The spawn template of the 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 #number range Range of the unit in meters. +-- @field #number speedmax Maximum speed in km/h the unit can do. -- @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 #WAREHOUSE warehouse Requesting warehouse. +-- @field Wrapper.Airbase#AIRBASE airbase Requesting airbase or airbase beloning to requesting warehouse. -- @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. +-- @field #number ndelivered Number of groups delivered to destination. Is managed automatically. +-- @field #number ntransporthome Number of transports back home. Is managed automatically. +-- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. Is managed automatically. +-- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. Is managed automatically. --- Descriptors enumerator describing the type of the asset in stock. -- @type WAREHOUSE.Descriptor @@ -159,7 +171,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.3" +WAREHOUSE.version="0.1.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehuse todo list. @@ -169,14 +181,14 @@ WAREHOUSE.version="0.1.3" -- DONE: Add AI_CARGO_AIRPLANE -- DONE: Add AI_CARGO_APC -- DONE: Add AI_CARGO_HELICOPTER +-- DONE: Switch to AI_CARGO_XXX_DISPATCHER +-- DONE: Add queue. -- TODO: Write documentation. -- TODO: Put active groups into the warehouse, e.g. when they were transported to this warehouse. -- TODO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. -- TODO: Handle cases with immobile units. --- DONE: Add queue. -- TODO: How to handle multiple units in a transport group? --- DONE: Switch to AI_CARGO_XXX_DISPATCHER --- TODO: Add phyical object, if destroyed asssets are gone. +-- DONE: Add phyical object, if destroyed asssets are gone. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -184,38 +196,61 @@ WAREHOUSE.version="0.1.3" --- 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. +-- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. -- @return #WAREHOUSE self -function WAREHOUSE:NewAirbase(airbase) - BASE:E({airbase=airbase:GetName()}) +function WAREHOUSE:New(warehouse) + BASE:E({warehouse=warehouse:GetName()}) + + if warehouse==nil then + BASE:E("ERROR: Warehouse does not exist!") + return nil + end -- Print version. - env.info(string.format("Adding warehouse v%s for airbase %s", WAREHOUSE.version, airbase:GetName())) + env.info(string.format("Adding warehouse v%s for structure %s", WAREHOUSE.version, warehouse: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()) + self.wid=string.format("WAREHOUSE %s | ", warehouse:GetName()) -- Set some variables. - self.homebase=airbase - self.coordinate=airbase:GetCoordinate() - self.coalition=airbase:GetCoalition() - self.country=airbase:GetCountry() - self.category=airbase:GetDesc().category - self.uid=airbase:GetID() + self.warehouse=warehouse + self.uid=warehouse:GetID() + self.coalition=warehouse:GetCoalition() + self.country=warehouse:GetCountry() + self.coordinate=warehouse:GetCoordinate() - -- Get the closest point on road. - local _road=self.coordinate:GetClosestPointToRoad():GetVec2() + --TODO: Add max range parameter to check if airbase is close enough. Range should be? ~3 km maybe? + local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) + + if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 2500 then + self.airbase=_airbase + self.category=self.airbase:GetDesc().category + end + + + -- Get the closest point on road and rail. + local _road=self.coordinate:GetClosestPointToRoad() + local _rail=self.coordinate:GetClosestPointToRoad(true) + + if _road and _road:Get2DDistance(self.coordinate) < 2500 then + self.road=_road + end + if _rail and _rail:Get2DDistance(self.coordinate) < 2500 then + self.rail=_rail + end + -- Define the default spawn zone. - self.spawnzone=ZONE_RADIUS:New("Spawnzone",_road, 200) + self.spawnzone=ZONE_RADIUS:New("Spawnzone", warehouse:GetVec2(), 200) -- Add FSM transitions. self:AddTransition("*", "Start", "Running") self:AddTransition("*", "Status", "*") self:AddTransition("*", "Request", "*") + self:AddTransition("*", "Arrived", "*") self:AddTransition("*", "Delivered", "*") --- Triggers the FSM event "Start". Starts the warehouse. @@ -250,16 +285,28 @@ function WAREHOUSE:NewAirbase(airbase) -- @param #WAREHOUSE.Queueitem Request Information table of the request. + --- Triggers the FSM event "Arrived", i.e. when a group has arrived at the destination. + -- @function [parent=#WAREHOUSE] Arrived + -- @param #WAREHOUSE self + -- @param Wrapper.Group#GROUP group Group that has arrived. + + --- Triggers the FSM event "Arrived" after a delay, i.e. when a group has arrived at the destination. + -- @function [parent=#WAREHOUSE] __Arrived + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param Wrapper.Group#GROUP group Group that has arrived. + + --- 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. + -- @param Core.Set#SET_GROUP groupset Set of groups that were 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. + -- @param Core.Set#SET_GROUP groupset Set of groups that were delivered. return self end @@ -274,14 +321,22 @@ end -- @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, country %d", self.homebase:GetName(), self.category, self.coalition, self.country)) + + -- Short info. + local text=string.format("Starting warehouse %s:\n",self.warehouse:GetName()) + text=text..string.format("Coaliton = %d\n", self.coalition) + text=text..string.format("Country = %d\n", self.country) + text=text..string.format("Airbase = %s (%s)\n", tostring(self.airbase:GetName()), tostring(self.category)) + env.info(text) + + -- Save self in static object. Easier to retrieve later. + self.warehouse:SetState(self.warehouse, "WAREHOUSE", self) -- Debug mark spawn zone. - --self.spawnzone:BoundZone(60, country.id.GERMANY) self.spawnzone:BoundZone(60, self.country) - self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.homebase:GetName()) + self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.warehouse:GetName()) - -- handle events + -- Handle events: -- event takeoff -- event landing -- event crash/dead @@ -298,7 +353,11 @@ end -- @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())) + self:E(self.wid..string.format("Checking warehouse status of %s", self.warehouse:GetName())) + + -- Print queue. + self:_PrintQueue(self.queue, "Queue0:") + self:_PrintQueue(self.pending, "Pending0:") -- Create a mark with the current assets in stock. if self.markerid~=nil then @@ -324,8 +383,9 @@ function WAREHOUSE:onafterStatus(From, Event, To) end -- Print queue. - self:_PrintQueue() - + self:_PrintQueue(self.queue, "Queue:") + self:_PrintQueue(self.pending, "Pending:") + -- Check queue and handle requests if possible. local request=self:_CheckQueue() @@ -334,12 +394,107 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:Request(request) end + -- Print queue. + self:_PrintQueue(self.queue, "Queue2:") + self:_PrintQueue(self.pending, "Pending2:") + -- Call status again in 30 sec. - self:__Status(10) + self:__Status(30) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Spawns requested asset at warehouse or associated airbase. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem Request Information table of the request. +-- @return Core.Set#SET_GROUP Set of groups that were spawned. +-- @return #WAREHOUSE.Attribute Generalized attribute of asset. +-- @return DCS#Group.Category Category of asset, i.e. ground, air, ship, ... +function WAREHOUSE:_SpawnAssetRequest(Request) + + -- Filter the requested cargo assets. + local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + + if #_assetstock==0 then + return nil,nil,nil + end + + -- General type and category. + local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute + local _cargocategory=_assetstock[1].category --DCS#Group.Category + + -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + Parking=self:_GetParkingForAssets(_assetstock) + end + + -- Create an empty set. + local groupset=SET_GROUP:New():FilterDeads() + + -- Spawn the assets. + local _delid={} + local _spawngroups={} + + -- Loop over cargo requests. + 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. + local _alias=self:_Alias(_assetitem,Request) + + -- Spawn object. Spawn with ALIAS here or DCS crashes! + --local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + + local _group=nil --Wrapper.Group#GROUP + local _attribute=_assetitem.attribute + + if _assetitem.category==Group.Category.GROUND then + + -- Spawn ground troops. + _group=_spawn:SpawnFromCoordinate(spawncoord) + + elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then + + --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! + + -- Spawn air units. + _group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) + + elseif _assetitem.category==Group.Category.TRAIN then + + -- Spawn train. + local _railroad=self.coordinate:GetClosestPointToRoad(true) + if _railroad then + _group=_spawn:SpawnFromCoordinate(_railroad) + end + + end + + if _group then + --_spawngroups[i]=_group + groupset:AddGroup(_group) + table.insert(_delid,_assetitem.uid) + else + self:E(self.wid.."ERROR: cargo asset could not be spawned!") + end + + end + + -- Delete spawned items from warehouse stock. + for _,_id in pairs(_delid) do + self:_DeleteStockItem(_id) + end + + return groupset,_cargotype,_cargocategory +end + --- On before "Request" event. Checks if the request can be fullfilled. -- @param #WAREHOUSE self -- @param #string From From state. @@ -348,14 +503,26 @@ end -- @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 %d assets of %s=%s by transport %s", - Request.airbase:GetName(), Request.nasset, tostring(Request.assetdesc), tostring(Request.assetdescval), tostring(Request.transporttype))) + --env.info(self.wid..string.format("Warehouse %s requesting %d assets of %s=%s by transport %s", + --Request.warehouse:GetName(), Request.nasset, tostring(Request.assetdesc), tostring(Request.assetdescval), tostring(Request.transporttype))) -- Distance from warehouse to requesting airbase. local distance=self.coordinate:Get2DDistance(Request.airbase:GetCoordinate()) -- Filter the requested assets. local _assets=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + + -- Check if destination is in range for all requested assets. + for _,_asset in pairs(_assets) do + local asset=_asset --#WAREHOUSE.Stockitem + if asset.range request denied. if #_assets < Request.nasset then @@ -376,99 +543,28 @@ end -- @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))) ------------------------------------------------------------------------------------------------------------------------------------ -- Cargo assets. ------------------------------------------------------------------------------------------------------------------------------------ - -- 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 cargo assets. - local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) - - -- General type and category. - local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute - local _cargocategory=_assetstock[1].category --DCS#Group.Category + -- Spawn assets. + local _spawngroups,_cargotype,_cargocategory=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP - -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - local Parking={} - if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_GetParkingForAssets(_assetstock) - end - - -- Spawn the assets. - local _delid={} - local _spawngroups={} + -- Add groups to cargo if they don't go by themselfs. + local CargoGroups --Core.Set#SET_CARGO + if Request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then - -- Loop over cargo requests. - 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_WID-%02d_AID-%04d_RID-%04d", _assetitem.unittype, self.uid,_assetitem.id,Request.uid) - local _alias=self:_Alias(_assetitem,Request) - --local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) - - local _group=nil --Wrapper.Group#GROUP - - -- Set a marker for the spawned group. - spawncoord:MarkToAll(string.format("Spawnpoint %s",_alias)) - - local _attribute=_assetitem.attribute - - 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 - - _group=_spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, true, Parking[i]) - - elseif _assetitem.category==Group.Category.TRAIN then - - -- Spawn train. - local _railroad=self.coordinate:GetClosestPointToRoad(true) - if _railroad then - _group=_spawn:SpawnFromCoordinate(_railroad) - end - - end - - if _group then - _spawngroups[i]=_group - table.insert(_delid,_assetitem.id) - - -- Add groups to cargo. - if Request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then - local cargogroup = CARGO_GROUP:New(_group, _alias, _alias, _loadradius, _nearradius) - CargoGroups:AddCargo(cargogroup) - end - else - self:E(self.wid.."ERROR: cargo asset could not be spawned!") + --TODO: make nearradius depended on transport type and asset type. + local _loadradius=5000 + local _nearradius=35 + CargoGroups = SET_CARGO:New() + for _,_group in pairs(_spawngroups:GetSetObjects()) do + local cargogroup = CARGO_GROUP:New(_group, _cargotype, "Peter", _loadradius, _nearradius) + CargoGroups:AddCargo(cargogroup) end end - -- Delete spawned items from warehouse stock. - for _,_id in pairs(_delid) do - self:_DeleteStockItem(_id) - end ------------------------------------------------------------------------------------------------------------------------------------ -- Self propelled assets. @@ -476,31 +572,52 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- No transport unit requested. Assets go by themselfes. if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - - for _i,_spawngroup in pairs(_spawngroups) do - + env.info("FF selfpropelled") + --for _i,_spawngroup in pairs(_spawngroups) do + for _,_spawngroup in pairs(_spawngroups:GetSetObjects()) do + local group=_spawngroup --Wrapper.Group#GROUP - local ToCoordinate=Request.airbase:GetZone():GetRandomCoordinate() + + local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate(50) + ToCoordinate:MarkToAll("Destination") + + local dist=Request.airbase:GetCoordinate():Get2DDistance(ToCoordinate) + + env.info("Dist to warehouse = "..dist) + env.info("FF group "..group:GetName()) -- Route cargo to their destination. - if _cargocategory==Group.Category.GROUND then + if _cargocategory==Group.Category.GROUND then + env.info("FF route ground "..group:GetName()) self:_RouteGround(group, ToCoordinate) elseif _cargocategory==Group.Category.AIRPLANE then + env.info("FF route plane "..group:GetName()) self:_RouteAir(group, Request.airbase) elseif _cargocategory==Group.Category.HELICOPTER then + env.info("FF route helo "..group:GetName()) self:_RouteAir(group, Request.airbase) elseif _cargocategory==Group.Category.SHIP then self:E("ERROR: self propelled ship not implemented yet!") elseif _cargocategory==Group.Category.TRAIN then + env.info("FF route train "..group:GetName()) self:_RouteTrain(group, ToCoordinate) + else + self:E(self.wid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory), tostring(group:GetName()))) end end - -- Delete request from queue. + -- Add cargo groups to request. + Request.cargogroupset=_spawngroups + Request.ndelivered=0 + + --local bla=UTILS.DeepCopy(Request) + + -- Add request to pending queue. table.insert(self.pending, Request) + -- Delete request from queue. self:_DeleteQueueItem(Request.uid) - + -- No cargo transport necessary. return end @@ -512,10 +629,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) env.info("FF cargo set name(s) = "..CargoGroups:GetObjectNames()) ---------------------------------------------------------------- - local TransportSet = SET_GROUP:New() + local TransportSet = SET_GROUP:New():FilterDeads() -- Pickup and deploy zones/bases. - local PickupAirbaseSet = SET_AIRBASE:New():AddAirbase(self.homebase) + local PickupAirbaseSet = SET_AIRBASE:New():AddAirbase(self.airbase) local DeployAirbaseSet = SET_AIRBASE:New():AddAirbase(Request.airbase) local DeployZoneSet = SET_ZONE:New():AddZone(Request.airbase:GetZone()) @@ -556,8 +673,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn plane at airport in uncontrolled state. local _takeoff=SPAWN.Takeoff.Cold --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) - local spawngroup=spawn:InitUnControlled(true):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) + local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + local spawngroup=spawn:InitUnControlled(true):SpawnAtAirbase(self.airbase,_takeoff, nil, nil, false, _parking) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -566,7 +683,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) - table.insert(_delid,_assetitem.id) + table.insert(_delid,_assetitem.uid) end end @@ -595,8 +712,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn helo at airport. local _takeoff=SPAWN.Takeoff.Hot --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) - local spawngroup=spawn:InitUnControlled(false):SpawnAtAirbase(self.homebase,_takeoff, nil, nil, false, _parking) + local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + local spawngroup=spawn:InitUnControlled(false):SpawnAtAirbase(self.airbase,_takeoff, nil, nil, false, _parking) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -605,7 +722,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) - table.insert(_delid,_assetitem.id) + table.insert(_delid,_assetitem.uid) else self:E(self.wid.."ERROR: spawngroup helo transport does not exist!") end @@ -620,7 +737,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportSet, CargoGroups, DeployZoneSet) -- Home zone. - CargoTransport:SetHomeBase(self.homebase) + CargoTransport:Setairbase(self.airbase) --CargoTransport:SetHomeZone(self.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.APC then @@ -637,8 +754,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _alias=self:_Alias(_assetitem, Request) -- Spawn plane at airport in uncontrolled state. - local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias) + --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) + local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) local spawngroup=spawn:SpawnFromCoordinate(self.spawnzone:GetRandomCoordinate()) if spawngroup then @@ -648,7 +765,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) - table.insert(_delid,_assetitem.id) + table.insert(_delid,_assetitem.uid) end end @@ -700,8 +817,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get warehouse state. local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - -- Trigger Delivered event. - warehouse:__Delivered(1, group) + -- Trigger Arrived event. + warehouse:__Arrived(1, group) end --- On after BackHome event. @@ -727,21 +844,119 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "Delivered" event. +--- On after "Arrived" event. Triggered when a group has arrived at its destination. -- @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) - self:E(self.wid..string.format("Cargo %s delivered!", group:GetName())) +function WAREHOUSE:onafterArrived(From, Event, To, group) + + self:E(self.wid..string.format("Cargo %s arrived!", tostring(group:GetName()))) + group:SmokeOrange() + -- Update pending request. + self:_UpdatePending(group) + + -- Update pending + + --[[ -- Route a ground group to the closest point on road. if group:IsGround() and group:GetSpeedMax()>0 then local road=group:GetCoordinate():GetClosestPointToRoad() local speed=group:GetSpeedMax()*0.6 group:RouteGroundTo(road, speed, "Off Road") end + ]] +end + +--- Update the pending requests by removing assets that have arrived. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group that has arrived at its destination. +function WAREHOUSE:_UpdatePending(group) + + -- Get request from group name. + local request=self:_GetRequestOfGroup(group) + + -- Increase number of delivered assets. + request.ndelivered=request.ndelivered+1 + + -- TODO: Well this does not handle the case when a group that has been delivered was killed! + if request.ndelivered>=request.cargogroupset:Count() then + self:__Delivered(5, request.cargogroupset) + end +end + +--- Get the request from the group. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which the info is gathered. +-- @return #WAREHOUSE.Queueitem The request belonging to this group. +function WAREHOUSE:_GetRequestOfGroup(group) + + local wid,aid,rid=self:_GetInfoFromGroup(group) + + for _,_request in pairs(self.pending) do + local request=_request --#WAREHOUSE.Queueitem + if request.uid==rid then + return request + end + end + +end + +--- Get warehouse id, asset id and request id from group name. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which the info is gathered. +function WAREHOUSE:_GetInfoFromGroup(group) + + ---@param #string text The text to analyse. + local function analyse(text) + + -- Get rid of #0001 tail from spawn. + local unspawned=UTILS.Split(text, "#")[1] + + -- Split keywords. + local keywords=UTILS.Split(unspawned, "_") + + local _wid=nil + local _aid=nil + local _rid=nil + + -- loop + for _,keys in pairs(keywords) do + local str=UTILS.Split(keys,"-") + local key=str[1] + local val=str[2] + if key:find("WID") then + _wid=tonumber(val) + elseif key:find("AID") then + _aid=tonumber(val) + elseif key:find("RID") then + _rid=tonumber(val) + end + end + + return _wid,_aid,_rid + end + + local name=group:GetName() + env.info("FF Name = "..tostring(name)) + local wid,aid,rid=analyse(name) + env.info("FF warehouse id = %s"..tostring(wid)) + env.info("FF asset id = %s"..tostring(aid)) + env.info("FF request id = %s"..tostring(rid)) + +end + +--- On after "Delivered" event. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered. +function WAREHOUSE:onafterDelivered(From, Event, To, groupset) + + env.info("FF all assets delivered!") end @@ -766,28 +981,36 @@ function WAREHOUSE:AddAsset(templategroupname, ngroups) local DCSgroup=group:GetDCSObject() local DCSunit=DCSgroup:getUnit(1) local DCSdesc=DCSunit:getDesc() - local DCSdisplay=DCSunit:getDesc().displayName + local DCSdisplay=DCSdesc.displayName local DCScategory=DCSgroup:getCategory() local DCStype=DCSunit:getTypeName() + local SpeedMax=group:GetSpeedMax() + local RangeMin=group:GetRange() 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}) + env.info(string.format("speed max = %s", tostring(SpeedMax))) + env.info(string.format("range min = %s", tostring(RangeMin))) + self:E({fullassetdesc=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.uid=self.assetid stockitem.templatename=templategroupname stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) stockitem.category=DCScategory stockitem.unittype=DCStype stockitem.attribute=attribute + stockitem.range=RangeMin + stockitem.speedmax=SpeedMax -- Modify the template so that the group is spawned with the right coalition. stockitem.template.CoalitionID=self.coalition @@ -818,16 +1041,31 @@ function WAREHOUSE:SetSpawnZone(zone) return self end ---- Add a request for the warehouse. +--- Add a delayed request for the warehouse. -- @param #WAREHOUSE self --- @param Wrapper.Airbase#AIRBASE Airbase airbase requesting supply. +-- @param #number delay Delay before the request is made in seconds. +-- @param #WAREHOUSE warehouse The warehouse 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) +function WAREHOUSE:__AddRequest(delay, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) + SCHEDULER:New(nil,WAREHOUSE.AddRequest,{self, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio}, delay) +end + + +--- Add a request for the warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE warehouse The warehouse 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(warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) nAsset=nAsset or 1 TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED @@ -838,25 +1076,28 @@ function WAREHOUSE:AddRequest(airbase, AssetDescriptor, AssetDescriptorValue, nA -- 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 --DCS#Airbase.Category - - -- No airplanes from to to FARPS. - 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 + + --[[ + local warehouse=nil --#WAREHOUSE + local airbase=nil --Wrapper.Airbase#AIRBASE + if requestor:IsInstanceOf("AIRBASE") then + airbase=requestor + elseif requestor:IsInstanceOf("WAREHOUSE") then + warehouse=requestor + airbase=warehouse.airbase end - + ]] + local request_air=false local request_plane=false local request_helo=false local request_ground=false local request_naval=false + local request_category=warehouse.category or -1 --DCS#Airbase.Category -- Check if category is matching if AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then + if AssetDescriptorValue==Group.Category.AIRPLANE then request_plane=true elseif AssetDescriptorValue==Group.Category.HELICOPTER then @@ -871,6 +1112,7 @@ function WAREHOUSE:AddRequest(airbase, AssetDescriptor, AssetDescriptorValue, nA self:E("ERROR: incorrect request. Asset Descriptor missmatch! Has to be Group.Cagetory.AIRPLANE, ...") return end + end -- Check attribute is matching @@ -912,6 +1154,7 @@ function WAREHOUSE:AddRequest(airbase, AssetDescriptor, AssetDescriptorValue, nA end end + -- General air request. request_air=request_helo or request_plane if request_air then @@ -938,133 +1181,72 @@ function WAREHOUSE:AddRequest(airbase, AssetDescriptor, AssetDescriptorValue, nA elseif request_naval then end + + -- Check if transport is possible. + if TransportType~=nil then + if TransportType==WAREHOUSE.TransportType.AIRPLANE then + + -- No airplanes from or to FARPS. + if self.category==Airbase.Category.HELIPAD or request_category==Airbase.Category.HELIPAD then + self:E("ERROR: incorrect request. Warehouse or requestor is FARP. No transport by plane possible!") + return + end + + elseif TransportType==WAREHOUSE.TransportType.APC then + + -- Transport by ground units. + + elseif TransportType==WAREHOUSE.TransportType.HELICOPTER then + + -- Transport by helicopters ==> need FARP or AIRBASE, well not really... + + elseif TransportType==WAREHOUSE.TransportType.SELFPROPELLED then + + + elseif TransportType==WAREHOUSE.TransportType.SHIP then + + -- Transport by ship. + self:E("ERROR: incorrect request. Transport by SHIP not implemented yet!") + return + + elseif TransportType==WAREHOUSE.TransportType.TRAIN then + + -- Transport by train. + self:E("ERROR: incorrect request. Transport by TRAIN not implemented yet!") + return + + else + -- No match. + self:E("ERROR: incorrect request. Transport type unknown!") + 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} + local request={ + uid=self.queueid, + prio=Prio, + warehouse=warehouse, + airbase=warehouse.airbase, + category=request_category, + assetdesc=AssetDescriptor, + assetdescval=AssetDescriptorValue, + nasset=nAsset, + transporttype=TransportType, + ntransport=nTransport} -- Add request to queue. table.insert(self.queue, request) end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Helper functions +-- Routing functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Checks if the request can be fullfilled. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Queueitem qitem The request to be checked. --- @return #boolean If true, request can be executed. If false, something is not right. -function WAREHOUSE:_CheckRequest(request) - - local okay=true - - -- Check if number of requested assets is in stock. - local _assets=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) - - -- Get the attibute of the requested asset. - local _assetattribute=_assets[1].attribute - local _assetcategory=_assets[1].category - - -- Check if enough assets are in stock. - if request.nasset > #_assets then - local text=string.format("Request denied! Not enough assets currently available.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - - -- Check available parking for asset units. - local Parkingdata=self.homebase:GetParkingSpotsTable() - local Parking - if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then - Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) - if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - end - - - -- Check that a transport units. - if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then - - -- Transports in stock. - local _transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) - - -- Get the attibute of the transport units. - local _transportattribute=_transports[1].attribute - local _transportcategory=_transports[1].category - - -- Check if enough transport units are available. - if request.ntransport > #_transports then - local text=string.format("Request denied! Not enough transport units currently available.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - - -- Check available parking for transport units. - if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then - Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) - if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - end - - else - -- self propelled case. - - end - - return okay -end - - ---- Creates a unique name for spawned assets. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Stockitem _assetitem Asset for which the name is created. --- @param #WAREHOUSE.Queueitem _queueitem (Optional) Request specific name. --- @return #string Alias name "UnitType\_WID-%02d\_AID-%04d" -function WAREHOUSE:_Alias(_assetitem,_queueitem) - local _alias=string.format("%s_WID-%02d_AID-%04d", _assetitem.unittype, self.uid,_assetitem.id) - if _queueitem then - _alias=_alias..string.format("_RID-%04d",_queueitem.uid) - end - return _alias -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() - - -- 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=self:_CheckRequest(qitem) - if okay==true then - request=qitem - break - end - end - - -- Execute request. - return request -end - --- Route ground units to destination. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP Group The ground group. @@ -1077,36 +1259,28 @@ function WAREHOUSE:_RouteGround(Group, Coordinate, Speed) local _speed=Speed or Group:GetSpeedMax()*0.6 -- Create a - local Waypoints = Group:TaskGroundOnRoad(Coordinate, _speed, "Off Road", true) + local Waypoints, canroad = 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 ) + 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 #WAREHOUSE self +-- @param Wrapper.Group#GROUP Aircraft 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 + if Aircraft and Aircraft:IsAlive()~=nil then -- Set takeoff type. local Takeoff = SPAWN.Takeoff.Cold @@ -1116,6 +1290,7 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) -- Nil check if Template==nil then + self:E("ERROR: Template nil") return end @@ -1146,20 +1321,30 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) 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} - + + --[[ + -- tasks + local TaskCombo = {} + TaskCombo[#TaskCombo+1]=self:_SimpleTaskFunction("WAREHOUSE._Arrived2") + + ToWaypoint.task = {} + ToWaypoint.task.id = "ComboTask" + ToWaypoint.task.params = {} + ToWaypoint.task.params.tasks = TaskCombo + ]] + -- 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) - + + -- Respawn group at the current airbase. + env.info("FF Respawn at current airbase group = "..Aircraft:GetName().." name before") + local bla=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) + env.info("FF Respawn at current airbase group = "..bla:GetName().." name after") + + -- Handle event engine shutdown and trigger delivered event. + bla:HandleEvent(EVENTS.EngineShutdown, self._ArrivedEvent) + else + self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(Aircraft:GetName()), tostring(Aircraft:IsAlive()))) end end @@ -1189,9 +1374,188 @@ function WAREHOUSE:_RouteTrain(Group, Coordinate, Speed) 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, warehouse) + env.info(warehouse.wid..string.format("Group %s arrived", tostring(group:GetName()))) + --Trigger delivered event. + warehouse:__Arrived(1, group) +end + +--- Task function for last waypoint. Triggering the Delivered event. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group that arrived. +function WAREHOUSE:_Arrived2(group) + env.info(self.wid..string.format("Group %s arrived2", tostring(group:GetName()))) + --Trigger delivered event. + self:__Arrived(1, group) +end + +--- Arrived event if an air unit/group arrived at its destination. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA EventData Event data table. +function WAREHOUSE:_ArrivedEvent(EventData) + + local unit=EventData.IniUnit + unit:SmokeBlue() + + local group=EventData.IniGroup + self:__Arrived(1, group) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Helper functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Checks if the request can be fullfilled. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem qitem The request to be checked. +-- @return #boolean If true, request can be executed. If false, something is not right. +function WAREHOUSE:_CheckRequest(request) + + local okay=true + + -- Check if number of requested assets is in stock. + local _assets=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) + + -- Nothing in stock. + if #_assets==0 then + local text=string.format("Request denied! No assets for this request currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + return false + end + + -- Get the attibute of the requested asset. + local _assetattribute=_assets[1].attribute + local _assetcategory=_assets[1].category + + -- Check if enough assets are in stock. + if request.nasset > #_assets then + local text=string.format("Request denied! Not enough assets currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check available parking for asset units. + local Parkingdata + local Parking + if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then + Parkingdata=self.airbase:GetParkingSpotsTable() + Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + + -- Check that a transport units. + if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then + + -- Transports in stock. + local _transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) + + -- Get the attibute of the transport units. + local _transportattribute=_transports[1].attribute + local _transportcategory=_transports[1].category + + -- Check if enough transport units are available. + if request.ntransport > #_transports then + local text=string.format("Request denied! Not enough transport units currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check available parking for transport units. + if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then + Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + else + -- self propelled case. + + end + + return okay +end + + +--- Creates a unique name for spawned assets. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem _assetitem Asset for which the name is created. +-- @param #WAREHOUSE.Queueitem _queueitem (Optional) Request specific name. +-- @return #string Alias name "UnitType\_WID-%02d\_AID-%04d" +function WAREHOUSE:_Alias(_assetitem,_queueitem) + local _alias=string.format("%s_WID-%02d_AID-%04d", _assetitem.unittype, self.uid,_assetitem.uid) + if _queueitem then + _alias=_alias..string.format("_RID-%04d",_queueitem.uid) + end + return _alias +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() + + -- 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=self:_CheckRequest(qitem) + if okay==true then + request=qitem + break + end + end + + -- Execute request. + return request +end + +--- Simple task function. Can be used to call a function which has the warehouse and the executing group as parameters. +-- @param #WAREHOUSE self +-- @param #string Function The name of the function to call passed as string. +function WAREHOUSE:_SimpleTaskFunction(Function) + self:F2({Function}) + + -- Name of the warehouse (static) object. + local warehouse=self.warehouse:GetName() + + -- Task script. + local DCSScript = {} + DCSScript[#DCSScript+1] = string.format('env.info("FF hello simple task function") ') + DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:Find( ... ) ') -- The group that executes the task function. Very handy with the "...". + DCSScript[#DCSScript+1] = string.format('local mystatic=STATIC:FindByName(%s) ', warehouse) -- The static that holds the warehouse self object. + DCSScript[#DCSScript+1] = string.format('local warehouse = mygroup:GetState(mystatic, "WAREHOUSE") ') -- Get the warehouse self object from the static. + DCSScript[#DCSScript+1] = string.format('%s(warehouse, mygroup)', Function) -- Call the function, e.g. myfunction.(warehouse,mygroup) + + -- Create task. + local DCSTask = CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript))) + + return DCSTask +end + --- Get the proper terminal type based on generalized attribute of the group. --@param #WAREHOUSE self --@param #WAREHOUSE.Attribute _attribute Generlized attibute of unit. +--@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group. function WAREHOUSE:_GetTerminal(_attribute) local _terminal=AIRBASE.TerminalType.OpenBig @@ -1206,6 +1570,7 @@ function WAREHOUSE:_GetTerminal(_attribute) _terminal=AIRBASE.TerminalType.HelicopterUsable end + return _terminal end --- Get parking data for all air assets that need to be spawned at an airbase. @@ -1221,7 +1586,7 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) for j=1,#spots do for i=1,#parkingdata do if parkingdata[i].TerminalID==spots[j].TerminalID then - table.remove(parkingdata,j) + table.remove(parkingdata, i) break end end @@ -1229,7 +1594,7 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) end -- Get complete parking data of the airbase. - parkingdata=parkingdata or self.homebase:GetParkingSpotsTable() + parkingdata=parkingdata or self.airbase:GetParkingSpotsTable() local assetparking={} for i=1,#assetlist do @@ -1240,8 +1605,22 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) local nunits=#group:GetUnits() local terminal=self:_GetTerminal(asset.attribute) + --[[ + env.info("asset name = "..tostring(asset.templatename)) + env.info("asset attribute = "..tostring(asset.attribute)) + env.info("terminal type = "..tostring(terminal)) + env.info("parking spots = "..tostring(#parkingdata)) + ]] + -- Find appropiate parking spots for this group. - local spots=self.homebase:FindFreeParkingSpotForAircraft(group, terminal, nil, nil, nil, nil, nil, nil, parkingdata) + local spots=self.airbase:FindFreeParkingSpotForAircraft(group, terminal, nil, nil, nil, nil, nil, nil, parkingdata) + + for _,spot in pairs(spots) do + if spot then + local coord=spot.Coordinate --Core.Point#COORDINATE + coord:MarkToAll("Parking spot for "..asset.templatename.." id "..asset.uid.." terminal id "..spot.TerminalID) + end + end -- Not enough parking spots for this group. if #spots LengthDirect*10) or (LengthRoad/LengthOnRoad*100<5)) + + -- Debug info. + self:T(string.format("Length on road = %.3f km", LengthOnRoad/1000)) + self:T(string.format("Length directly = %.3f km", LengthDirect/1000)) + self:T(string.format("Length fraction = %.3f km", LengthOnRoad/LengthDirect)) + self:T(string.format("Length only road = %.3f km", LengthRoad/1000)) + self:T(string.format("Length off road = %.3f km", LengthOffRoad/1000)) + self:T(string.format("Percent on road = %.1f", LengthRoad/LengthOnRoad*100)) + + end + -- Route, ground waypoints along road. local route={} - - -- Length on road is 10 times longer than direct route or path on road is very short (<5% of total path). - local LongRoad=LengthOnRoad and ((LengthOnRoad > LengthDirect*10) or (LengthRoad/LengthOnRoad*100<5)) - + local canroad=false + -- Check if a valid path on road could be found. if PathOnRoad then - -- Check whether the road is very long compared to direct path. if LongRoad and Shortcut then @@ -2088,6 +2097,7 @@ do -- Route methods end + canroad=true else -- No path on road could be found (can happen!) ==> Route group directly from A to B. @@ -2096,7 +2106,7 @@ do -- Route methods end - return route + return route, canroad end --- Make a task for a TRAIN Controllable to drive towards a specific point using railroad. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 1cf57eb77..f78f9ef04 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -413,6 +413,38 @@ function GROUP:GetSpeedMax() return nil end +--- Returns the maximum range of the group. +-- If the group is heterogenious and consists of different units, the smallest range of all units is returned. +-- @param #GROUP self +-- @return #number Range in meters. +function GROUP:GetRange() + self:F2( self.GroupName ) + + local DCSGroup = self:GetDCSObject() + if DCSGroup then + + local Units=self:GetUnits() + + local Rangemin=nil + + for _,unit in pairs(Units) do + local unit=unit --Wrapper.Unit#UNIT + local range=unit:GetRange() + if range then + if Rangemin==nil then + Rangemin=range + elseif range Date: Mon, 13 Aug 2018 16:25:51 +0200 Subject: [PATCH 06/73] Warehouse v0.1.5 --- .../Moose/Functional/Warehouse.lua | 1055 +++++++++-------- 1 file changed, 563 insertions(+), 492 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 00c3714c0..c2d3cde99 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -29,7 +29,7 @@ -- @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.Point#COORDINATE road Closest point to warehouse on road. --- @field Core.Point#COORDINATE road Closest point to warehouse on rail. +-- @field Core.Point#COORDINATE rail Closest point to warehouse on rail. -- @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 uid Unit identifier of the warehouse. Derived from the associated airbase. @@ -59,13 +59,19 @@ -- ![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. +-- A warehouse is an abstract object represented by a physical (static) building that can hold virtual assets in stock. +-- It can but it must not be associated with a particular airbase. The associated airbase can be an airdrome, a Helipad/FARP or a ship. +-- +-- If another another warehouse requests assets, the corresponding troops are spawned at the warehouse and being transported to the requestor or go their +-- by themselfs. Once arrived at the requesting warehouse, the assets go into the stock of the requestor and can be reactivated when necessary. -- -- ## 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. +-- +-- # Adding Assets -- +-- # Requests -- -- -- === @@ -83,7 +89,7 @@ WAREHOUSE = { coalition = nil, country = nil, airbase = nil, - category = nil, + category = -1, coordinate = nil, road = nil, rail = nil, @@ -171,10 +177,10 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.4" +WAREHOUSE.version="0.1.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Warehuse todo list. +-- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Add event handlers. @@ -197,10 +203,13 @@ WAREHOUSE.version="0.1.4" --- WAREHOUSE constructor. Creates a new WAREHOUSE object accociated with an airbase. -- @param #WAREHOUSE self -- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. +-- @param Core.Zone#ZONE spawnzone (Optional) The zone in which units are spawned and despawned when they leave or arrive the warehouse. Default is a zone of 200 meters around the warehouse. +-- @param Wrapper.Airbase#AIRBASE airbase (Optional) The airbase belonging to the warehouse. Default is the closest airbase to the warehouse structure as long as it within a range of 3 km. -- @return #WAREHOUSE self -function WAREHOUSE:New(warehouse) +function WAREHOUSE:New(warehouse, spawnzone, airbase) BASE:E({warehouse=warehouse:GetName()}) + -- Nil check. if warehouse==nil then BASE:E("ERROR: Warehouse does not exist!") return nil @@ -222,29 +231,35 @@ function WAREHOUSE:New(warehouse) self.country=warehouse:GetCountry() self.coordinate=warehouse:GetCoordinate() - --TODO: Add max range parameter to check if airbase is close enough. Range should be? ~3 km maybe? - local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) - - if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 2500 then - self.airbase=_airbase - self.category=self.airbase:GetDesc().category + -- Set airbase. + if airbase then + -- User requested. + self.airbase=airbase + else + -- Closest of the same coalition but within a certain range. + local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) + if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 3000 then + self.airbase=_airbase + self.category=self.airbase:GetDesc().category + end end - - + -- Get the closest point on road and rail. local _road=self.coordinate:GetClosestPointToRoad() local _rail=self.coordinate:GetClosestPointToRoad(true) - - if _road and _road:Get2DDistance(self.coordinate) < 2500 then - self.road=_road - end - if _rail and _rail:Get2DDistance(self.coordinate) < 2500 then - self.rail=_rail - end + -- Set connections. + if _road and _road:Get2DDistance(self.coordinate) < 3000 then + self.road=_road + _road:MarkToAll(string.format("%s road connection.", self.warehouse:GetName()), true) + end + if _rail and _rail:Get2DDistance(self.coordinate) < 3000 then + self.rail=_rail + _rail:MarkToAll(string.format("%s rail connection.", self.warehouse:GetName()), true) + end -- Define the default spawn zone. - self.spawnzone=ZONE_RADIUS:New("Spawnzone", warehouse:GetVec2(), 200) + self.spawnzone=ZONE_RADIUS:New(string.format("Spawnzone %s",warehouse:GetName()), warehouse:GetVec2(), 200) -- Add FSM transitions. self:AddTransition("*", "Start", "Running") @@ -311,6 +326,137 @@ function WAREHOUSE:New(warehouse) return self 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=DCSdesc.displayName + local DCScategory=DCSgroup:getCategory() + local DCStype=DCSunit:getTypeName() + local SpeedMax=group:GetSpeedMax() + local RangeMin=group:GetRange() + + 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)) + env.info(string.format("speed max = %s", tostring(SpeedMax))) + env.info(string.format("range min = %s", tostring(RangeMin))) + self:E({fullassetdesc=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.uid=self.assetid + stockitem.templatename=templategroupname + stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) + stockitem.category=DCScategory + stockitem.unittype=DCStype + stockitem.attribute=attribute + stockitem.range=RangeMin + stockitem.speedmax=SpeedMax + + -- Modify the template so that the group is spawned with the right coalition. + stockitem.template.CoalitionID=self.coalition + stockitem.template.CountryID=self.country + + table.insert(self.stock, stockitem) + end + + -- Destroy group if it is alive. + if group:IsAlive()==true then + group:Destroy() + 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 + +--- Add a request for the warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE warehouse The warehouse 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(warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) + + nAsset=nAsset or 1 + TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED + nTransport=nTransport or 1 + Prio=Prio or 50 + + -- Increase id. + self.queueid=self.queueid+1 + + -- Request queue table item. + local request={ + uid=self.queueid, + prio=Prio, + warehouse=warehouse, + airbase=warehouse.airbase, + category=warehouse.category, + assetdesc=AssetDescriptor, + assetdescval=AssetDescriptorValue, + nasset=nAsset, + transporttype=TransportType, + ntransport=nTransport} + + -- Add request to queue. + table.insert(self.queue, request) +end + +--- Add a delayed request for the warehouse. +-- @param #WAREHOUSE self +-- @param #number delay Delay before the request is made in seconds. +-- @param #WAREHOUSE warehouse The warehouse 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(delay, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) + SCHEDULER:New(nil,WAREHOUSE.AddRequest,{self, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio}, delay) +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 ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -342,6 +488,11 @@ function WAREHOUSE:onafterStart(From, Event, To) -- event crash/dead -- event base captured ==> change coalition ==> add assets to other coalition + -- Handle event that airbase was captured. + if self.airbase then + --self.airbase:HandleEvent(EVENTS.BaseCaptured, self._BaseCaptured) + end + self:__Status(5) end @@ -404,97 +555,6 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Spawns requested asset at warehouse or associated airbase. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Queueitem Request Information table of the request. --- @return Core.Set#SET_GROUP Set of groups that were spawned. --- @return #WAREHOUSE.Attribute Generalized attribute of asset. --- @return DCS#Group.Category Category of asset, i.e. ground, air, ship, ... -function WAREHOUSE:_SpawnAssetRequest(Request) - - -- Filter the requested cargo assets. - local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) - - if #_assetstock==0 then - return nil,nil,nil - end - - -- General type and category. - local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute - local _cargocategory=_assetstock[1].category --DCS#Group.Category - - -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - local Parking={} - if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_GetParkingForAssets(_assetstock) - end - - -- Create an empty set. - local groupset=SET_GROUP:New():FilterDeads() - - -- Spawn the assets. - local _delid={} - local _spawngroups={} - - -- Loop over cargo requests. - 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. - local _alias=self:_Alias(_assetitem,Request) - - -- Spawn object. Spawn with ALIAS here or DCS crashes! - --local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - - local _group=nil --Wrapper.Group#GROUP - local _attribute=_assetitem.attribute - - if _assetitem.category==Group.Category.GROUND then - - -- Spawn ground troops. - _group=_spawn:SpawnFromCoordinate(spawncoord) - - elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then - - --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! - - -- Spawn air units. - _group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) - - elseif _assetitem.category==Group.Category.TRAIN then - - -- Spawn train. - local _railroad=self.coordinate:GetClosestPointToRoad(true) - if _railroad then - _group=_spawn:SpawnFromCoordinate(_railroad) - end - - end - - if _group then - --_spawngroups[i]=_group - groupset:AddGroup(_group) - table.insert(_delid,_assetitem.uid) - else - self:E(self.wid.."ERROR: cargo asset could not be spawned!") - end - - end - - -- Delete spawned items from warehouse stock. - for _,_id in pairs(_delid) do - self:_DeleteStockItem(_id) - end - - return groupset,_cargotype,_cargocategory -end - --- On before "Request" event. Checks if the request can be fullfilled. -- @param #WAREHOUSE self -- @param #string From From state. @@ -520,7 +580,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) --TODO Delete request from queue because it will never be possible! - return false + return false end end @@ -536,7 +596,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) end ---- On after "Request" event. Initiates the transport of the assets to the requesting airbase. +--- On after "Request" event. Initiates the transport of the assets to the requesting warehouse. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -565,7 +625,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end end - ------------------------------------------------------------------------------------------------------------------------------------ -- Self propelled assets. ------------------------------------------------------------------------------------------------------------------------------------ @@ -573,19 +632,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- No transport unit requested. Assets go by themselfes. if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then env.info("FF selfpropelled") - --for _i,_spawngroup in pairs(_spawngroups) do + for _,_spawngroup in pairs(_spawngroups:GetSetObjects()) do local group=_spawngroup --Wrapper.Group#GROUP local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate(50) ToCoordinate:MarkToAll("Destination") - - local dist=Request.airbase:GetCoordinate():Get2DDistance(ToCoordinate) - - env.info("Dist to warehouse = "..dist) - env.info("FF group "..group:GetName()) - + -- Route cargo to their destination. if _cargocategory==Group.Category.GROUND then env.info("FF route ground "..group:GetName()) @@ -615,6 +669,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add request to pending queue. table.insert(self.pending, Request) + -- Delete request from queue. self:_DeleteQueueItem(Request.uid) @@ -626,9 +681,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Transport assets and dispachers ------------------------------------------------------------------------------------------------------------------------------------ - env.info("FF cargo set name(s) = "..CargoGroups:GetObjectNames()) - ---------------------------------------------------------------- - + -- Set of cargo carriers. local TransportSet = SET_GROUP:New():FilterDeads() -- Pickup and deploy zones/bases. @@ -646,9 +699,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _transporttype=_assetstock[1].attribute --#WAREHOUSE.Attribute local _transportcategory=_assetstock[1].category --DCS#Group.Category - -- Now we try to find all parking spots for all transport groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - local Parking={} - -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then @@ -750,7 +800,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem -- Spawn with ALIAS here or DCS crashes! - --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) local _alias=self:_Alias(_assetitem, Request) -- Spawn plane at airport in uncontrolled state. @@ -782,12 +831,12 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then - self:E(self.wid.."ERROR: transport by train not supported yet!") + self:E(self.wid.."ERROR: cargo 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!") + self:E(self.wid.."ERROR: cargo transport by ship not supported yet!") return elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -836,12 +885,106 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Start dispatcher. CargoTransport:__Start(5) - -- Delete request from queue. + -- Add request to pending queue. table.insert(self.pending, Request) + + -- Delete request from queue. self:_DeleteQueueItem(Request.uid) end + +--- Spawns requested asset at warehouse or associated airbase. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem Request Information table of the request. +-- @return Core.Set#SET_GROUP Set of groups that were spawned. +-- @return #WAREHOUSE.Attribute Generalized attribute of asset. +-- @return DCS#Group.Category Category of asset, i.e. ground, air, ship, ... +function WAREHOUSE:_SpawnAssetRequest(Request) + + -- Filter the requested cargo assets. + local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + + if #_assetstock==0 then + return nil,nil,nil + end + + -- General type and category. + local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute + local _cargocategory=_assetstock[1].category --DCS#Group.Category + + -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + Parking=self:_GetParkingForAssets(_assetstock) + end + + -- Create an empty set. + local groupset=SET_GROUP:New():FilterDeads() + + -- Spawn the assets. + local _delid={} + local _spawngroups={} + + -- Loop over cargo requests. + 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. + local _alias=self:_Alias(_assetitem, Request) + + -- Spawn object. Spawn with ALIAS here or DCS crashes! + --local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + + local _group=nil --Wrapper.Group#GROUP + local _attribute=_assetitem.attribute + + if _assetitem.category==Group.Category.GROUND then + + -- Spawn ground troops. + _group=_spawn:SpawnFromCoordinate(spawncoord) + + elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then + + --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! + + -- Spawn air units. + _group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) + + elseif _assetitem.category==Group.Category.TRAIN then + + -- Spawn train. + if self.rail then + --TODO: Rail should only get one asset because they would spawn on top! + _group=_spawn:SpawnFromCoordinate(self.rail) + end + + end + + if _group then + --_spawngroups[i]=_group + groupset:AddGroup(_group) + table.insert(_delid,_assetitem.uid) + else + self:E(self.wid.."ERROR: cargo asset could not be spawned!") + end + + end + + -- Delete spawned items from warehouse stock. + for _,_id in pairs(_delid) do + self:_DeleteStockItem(_id) + end + + return groupset,_cargotype,_cargocategory +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "Arrived" event. Triggered when a group has arrived at its destination. @@ -858,16 +1001,6 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) -- Update pending request. self:_UpdatePending(group) - -- Update pending - - --[[ - -- Route a ground group to the closest point on road. - if group:IsGround() and group:GetSpeedMax()>0 then - local road=group:GetCoordinate():GetClosestPointToRoad() - local speed=group:GetSpeedMax()*0.6 - group:RouteGroundTo(road, speed, "Off Road") - end - ]] end --- Update the pending requests by removing assets that have arrived. @@ -876,77 +1009,21 @@ end function WAREHOUSE:_UpdatePending(group) -- Get request from group name. - local request=self:_GetRequestOfGroup(group) + local request=self:_GetRequestOfGroup(group, self.pending) -- Increase number of delivered assets. request.ndelivered=request.ndelivered+1 + -- Number of alive groups. + local nalive=request.cargogroupset:Count() + -- TODO: Well this does not handle the case when a group that has been delivered was killed! - if request.ndelivered>=request.cargogroupset:Count() then - self:__Delivered(5, request.cargogroupset) + if request.ndelivered>=nalive then + self:__Delivered(5, request.cargogroupset, request) end end ---- Get the request from the group. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group from which the info is gathered. --- @return #WAREHOUSE.Queueitem The request belonging to this group. -function WAREHOUSE:_GetRequestOfGroup(group) - local wid,aid,rid=self:_GetInfoFromGroup(group) - - for _,_request in pairs(self.pending) do - local request=_request --#WAREHOUSE.Queueitem - if request.uid==rid then - return request - end - end - -end - ---- Get warehouse id, asset id and request id from group name. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group from which the info is gathered. -function WAREHOUSE:_GetInfoFromGroup(group) - - ---@param #string text The text to analyse. - local function analyse(text) - - -- Get rid of #0001 tail from spawn. - local unspawned=UTILS.Split(text, "#")[1] - - -- Split keywords. - local keywords=UTILS.Split(unspawned, "_") - - local _wid=nil - local _aid=nil - local _rid=nil - - -- loop - for _,keys in pairs(keywords) do - local str=UTILS.Split(keys,"-") - local key=str[1] - local val=str[2] - if key:find("WID") then - _wid=tonumber(val) - elseif key:find("AID") then - _aid=tonumber(val) - elseif key:find("RID") then - _rid=tonumber(val) - end - end - - return _wid,_aid,_rid - end - - local name=group:GetName() - env.info("FF Name = "..tostring(name)) - local wid,aid,rid=analyse(name) - env.info("FF warehouse id = %s"..tostring(wid)) - env.info("FF asset id = %s"..tostring(aid)) - env.info("FF request id = %s"..tostring(rid)) - -end --- On after "Delivered" event. -- @param #WAREHOUSE self @@ -954,293 +1031,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered. -function WAREHOUSE:onafterDelivered(From, Event, To, groupset) +-- @param #WAREHOUSE.Queueitem request +function WAREHOUSE:onafterDelivered(From, Event, To, groupset, request) env.info("FF all assets delivered!") -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=DCSdesc.displayName - local DCScategory=DCSgroup:getCategory() - local DCStype=DCSunit:getTypeName() - local SpeedMax=group:GetSpeedMax() - local RangeMin=group:GetRange() - - 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)) - env.info(string.format("speed max = %s", tostring(SpeedMax))) - env.info(string.format("range min = %s", tostring(RangeMin))) - self:E({fullassetdesc=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.uid=self.assetid - stockitem.templatename=templategroupname - stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) - stockitem.category=DCScategory - stockitem.unittype=DCStype - stockitem.attribute=attribute - stockitem.range=RangeMin - stockitem.speedmax=SpeedMax - - -- Modify the template so that the group is spawned with the right coalition. - stockitem.template.CoalitionID=self.coalition - stockitem.template.CountryID=self.country - - table.insert(self.stock, stockitem) - end - - -- Destroy group if it is alive. - if group:IsAlive()==true then - group:Destroy() - 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 - ---- 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 - ---- Add a delayed request for the warehouse. --- @param #WAREHOUSE self --- @param #number delay Delay before the request is made in seconds. --- @param #WAREHOUSE warehouse The warehouse 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(delay, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) - SCHEDULER:New(nil,WAREHOUSE.AddRequest,{self, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio}, delay) -end - - ---- Add a request for the warehouse. --- @param #WAREHOUSE self --- @param #WAREHOUSE warehouse The warehouse 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(warehouse, 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 warehouse=nil --#WAREHOUSE - local airbase=nil --Wrapper.Airbase#AIRBASE - if requestor:IsInstanceOf("AIRBASE") then - airbase=requestor - elseif requestor:IsInstanceOf("WAREHOUSE") then - warehouse=requestor - airbase=warehouse.airbase - end - ]] - - local request_air=false - local request_plane=false - local request_helo=false - local request_ground=false - local request_naval=false - local request_category=warehouse.category or -1 --DCS#Airbase.Category - - -- Check if category is matching - if AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then - - if AssetDescriptorValue==Group.Category.AIRPLANE then - request_plane=true - elseif AssetDescriptorValue==Group.Category.HELICOPTER then - request_helo=true - elseif AssetDescriptorValue==Group.Category.GROUND then - request_ground=true - elseif AssetDescriptorValue==Group.Category.SHIP then - request_naval=true - elseif AssetDescriptorValue==Group.Category.TRAIN then - request_ground=true - else - self:E("ERROR: incorrect request. Asset Descriptor missmatch! Has to be Group.Cagetory.AIRPLANE, ...") - return - end - + -- Put assets in new warehouse. + for _,_group in pairs(groupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + request.warehouse:AddAsset(group:GetTemplate(), 1) end - -- Check attribute is matching - if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then - if AssetDescriptorValue==WAREHOUSE.Attribute.ARTILLERY then - request_ground=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.ATTACKHELICOPTER then - request_helo=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.AWACS then - request_plane=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.BOMBER then - request_plane=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.FIGHTER then - request_plane=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.INFANTRY then - request_ground=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.OTHER then - self:E("ERROR: incorrect request. Asset attribute WAREHOUSE.Attribute.OTHER is not valid!") - return - elseif AssetDescriptorValue==WAREHOUSE.Attribute.SHIP then - request_naval=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TANK then - request_ground=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TANKER then - request_plane=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRAIN then - request_ground=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRANSPORT_APC then - request_ground=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRANSPORT_HELO then - request_helo=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRANSPORT_PLANE then - request_plane=true - elseif AssetDescriptorValue==WAREHOUSE.Attribute.TRUCK then - request_ground=true - else - self:E("ERROR: incorrect request. Unknown asset attribute!") - return - end - end - - -- General air request. - request_air=request_helo or request_plane - - if request_air then - - if request_plane then - -- No airplane to or from FARPS. - if request_category==Airbase.Category.HELIPAD or self.category==Airbase.Category.HELIPAD then - self:E("ERROR: incorrect request. Asset aircraft requestst but warehouse or requestor is HELIPAD/FARP!") - return - end - elseif request_helo then - - end - - elseif request_ground then - - -- No ground assets directly to ships. - if (request_category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) - and not (TransportType==WAREHOUSE.TransportType.AIRPLANE or TransportType==WAREHOUSE.TransportType.HELICOPTER) then - self:E("ERROR: incorrect request. Ground asset requested but warehouse or requestor is SHIP!") - return - end - - elseif request_naval then - - end - - -- Check if transport is possible. - if TransportType~=nil then - if TransportType==WAREHOUSE.TransportType.AIRPLANE then - - -- No airplanes from or to FARPS. - if self.category==Airbase.Category.HELIPAD or request_category==Airbase.Category.HELIPAD then - self:E("ERROR: incorrect request. Warehouse or requestor is FARP. No transport by plane possible!") - return - end - - elseif TransportType==WAREHOUSE.TransportType.APC then - - -- Transport by ground units. - - elseif TransportType==WAREHOUSE.TransportType.HELICOPTER then - - -- Transport by helicopters ==> need FARP or AIRBASE, well not really... - - elseif TransportType==WAREHOUSE.TransportType.SELFPROPELLED then - - - elseif TransportType==WAREHOUSE.TransportType.SHIP then - - -- Transport by ship. - self:E("ERROR: incorrect request. Transport by SHIP not implemented yet!") - return - - elseif TransportType==WAREHOUSE.TransportType.TRAIN then - - -- Transport by train. - self:E("ERROR: incorrect request. Transport by TRAIN not implemented yet!") - return - - else - -- No match. - self:E("ERROR: incorrect request. Transport type unknown!") - return - end - - end - - -- Increase id. - self.queueid=self.queueid+1 - - -- Request queue table item. - local request={ - uid=self.queueid, - prio=Prio, - warehouse=warehouse, - airbase=warehouse.airbase, - category=request_category, - assetdesc=AssetDescriptor, - assetdescval=AssetDescriptorValue, - nasset=nAsset, - transporttype=TransportType, - ntransport=nTransport} - - -- Add request to queue. - table.insert(self.queue, request) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1256,9 +1057,10 @@ function WAREHOUSE:_RouteGround(Group, Coordinate, Speed) if Group and Group:IsAlive() then + -- Set speed. local _speed=Speed or Group:GetSpeedMax()*0.6 - -- Create a + -- Create task. local Waypoints, canroad = Group:TaskGroundOnRoad(Coordinate, _speed, "Off Road", true) -- Task function triggering the arrived event. @@ -1409,11 +1211,215 @@ end -- Helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Checks if the request can be fullfilled. +--- Checks if the request can be fulfilled in general. If not, it is removed from the queue. +-- Check if departure and destination bases are of the right type. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Queueitem qitem The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. -function WAREHOUSE:_CheckRequest(request) +function WAREHOUSE:_CheckRequestValid() + + -- Requests to delete. + local delid={} + + for _,_request in pairs(self.queue) do + local request=_request --#WAREHOUSE.Queueitem + + local valid=true + + --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 asset_air=false + local asset_plane=false + local asset_helo=false + local asset_ground=false + local asset_train=false + local asset_naval=false + + -- Check if category was provided. + if request.assetdesc==WAREHOUSE.Descriptor.CATEGORY then + + if request.assetdescval==Group.Category.AIRPLANE then + asset_plane=true + elseif request.assetdescval==Group.Category.HELICOPTER then + asset_helo=true + elseif request.assetdescval==Group.Category.GROUND then + asset_ground=true + elseif request.assetdescval==Group.Category.SHIP then + asset_naval=true + elseif request.assetdescval==Group.Category.TRAIN then + asset_ground=true + asset_train=true + -- Only one train due to finding spawn placen on rail! + --nAsset=1 + else + self:E("ERROR: incorrect request. Asset Descriptor missmatch! Has to be Group.Cagetory.AIRPLANE, ...") + valid=false + end + + end + + -- Check attribute is matching + if request.assetdesc==WAREHOUSE.Descriptor.ATTRIBUTE then + if request.assetdescval==WAREHOUSE.Attribute.ARTILLERY then + asset_ground=true + elseif request.assetdescval==WAREHOUSE.Attribute.ATTACKHELICOPTER then + asset_helo=true + elseif request.assetdescval==WAREHOUSE.Attribute.AWACS then + asset_plane=true + elseif request.assetdescval==WAREHOUSE.Attribute.BOMBER then + asset_plane=true + elseif request.assetdescval==WAREHOUSE.Attribute.FIGHTER then + asset_plane=true + elseif request.assetdescval==WAREHOUSE.Attribute.INFANTRY then + asset_ground=true + elseif request.assetdescval==WAREHOUSE.Attribute.OTHER then + self:E("ERROR: incorrect request. Asset attribute WAREHOUSE.Attribute.OTHER is not valid!") + valid=false + elseif request.assetdescval==WAREHOUSE.Attribute.SHIP then + asset_naval=true + elseif request.assetdescval==WAREHOUSE.Attribute.TANK then + asset_ground=true + elseif request.assetdescval==WAREHOUSE.Attribute.TANKER then + asset_plane=true + elseif request.assetdescval==WAREHOUSE.Attribute.TRAIN then + asset_ground=true + elseif request.assetdescval==WAREHOUSE.Attribute.TRANSPORT_APC then + asset_ground=true + elseif request.assetdescval==WAREHOUSE.Attribute.TRANSPORT_HELO then + asset_helo=true + elseif request.assetdescval==WAREHOUSE.Attribute.TRANSPORT_PLANE then + asset_plane=true + elseif request.assetdescval==WAREHOUSE.Attribute.TRUCK then + asset_ground=true + else + self:E("ERROR: incorrect request. Unknown asset attribute!") + valid=false + end + end + + -- General air request. + asset_air=asset_helo or asset_plane + + if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then + + if asset_air then + + if asset_plane then + + -- No airplane to or from FARPS. + if request.category==Airbase.Category.HELIPAD or self.category==Airbase.Category.HELIPAD then + self:E("ERROR: incorrect request. Asset aircraft requestst but warehouse or requestor is HELIPAD/FARP!") + valid=false + end + + -- Category SHIP is not general enough! Fighters can go to carriers. Which fighters, is there an attibute? + -- Also for carriers, attibute? + + elseif asset_helo then + + -- Helos need a FARP or AIRBASE or SHIP for spawning. Event if they go there they "cannot" be spawned again. + -- Unless I allow spawning of helos in the the spawn zone. But one should place at least a FARP there. + if self.category==-1 or request.category==-1 then + self:E("ERROR: incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destinaion base!") + valid=false + end + + end + + elseif asset_ground then + + -- No ground assets directly to or from ships. + -- TODO: May needs refinement if warehouse is on land and requestor is ship in harbour?! + if (request.category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) then + self:E("ERROR: incorrect request. Ground asset requested but warehouse or requestor is SHIP!") + valid=false + end + + elseif asset_naval then + + self:E("ERROR: incorrect request. Naval units not supported yet!") + valid=false + + end + + else + + -- Assests need a transport. + + if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + + -- Airplanes only to AND from airdromes. + if self.category~=Airbase.Category.AIRDROME or request.category~=Airbase.Category.AIRDROME then + self:E("ERROR: incorrect request. Warehouse or requestor does not have an airdrome. No transport by plane possible!") + valid=false + end + + --TODO: Not sure if there are any transport planes that can land on a carrier? + + elseif request.transporttype==WAREHOUSE.TransportType.APC then + + -- Transport by ground units. + + -- No transport to or from ships + if self.category==Airbase.Category.SHIP or request.category==Airbase.Category.SHIP then + self:E("ERROR: incorrect request. Warehouse or requestor is SHIP. No transport by APC possible!") + valid=false + end + + elseif request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + + -- Transport by helicopters ==> need airbase for spawning but not for delivering to the zone. + if self.category==-1 then + self:E("ERROR: incorrect request. Warehouse has no airbase. Transport by helicopter not possible!") + valid=false + end + + elseif request.transporttype==WAREHOUSE.TransportType.SHIP then + + -- Transport by ship. + self:E("ERROR: incorrect request. Transport by SHIP not implemented yet!") + valid=false + + elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then + + -- Only one train due to limited spawn place. + --nTransport=1 + + -- Transport by train. + self:E("ERROR: incorrect request. Transport by TRAIN not implemented yet!") + valid=false + + else + -- No match. + self:E("ERROR: incorrect request. Transport type unknown!") + valid=false + end + + end + + if not valid then + table.insert(delid, request.id) + end + + end -- loop queue items. + + + -- Delete invalid requests. + for _,_uid in pairs(delid) do + self:_DeleteQueueItem(_uid) + end + +end + +--- Checks if the request can be fullfilled right now. +-- Check for current parking situation, number of assets and transports currently in stock +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem request The request to be checked. +-- @return #boolean If true, request can be executed. If false, something is not right. +function WAREHOUSE:_CheckRequestNow(request) local okay=true @@ -1518,7 +1524,7 @@ function WAREHOUSE:_CheckQueue() local request=nil --#WAREHOUSE.Queueitem for _,_qitem in ipairs(self.queue) do local qitem=_qitem --#WAREHOUSE.Queueitem - local okay=self:_CheckRequest(qitem) + local okay=self:_CheckRequestNow(qitem) if okay==true then request=qitem break @@ -1637,6 +1643,71 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) return assetparking, parkingdata end +--- Get the request belonging to a group. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which the info is gathered. +-- @param #table queue Queue holding all requests. +-- @return #WAREHOUSE.Queueitem The request belonging to this group. +function WAREHOUSE:_GetRequestOfGroup(group, queue) + + -- Get warehouse, asset and request ID from group name. + local wid,aid,rid=self:_GetInfoFromGroup(group) + + -- Find the request. + for _,_request in pairs(queue) do + local request=_request --#WAREHOUSE.Queueitem + if request.uid==rid then + return request + end + end + +end + +--- Get warehouse id, asset id and request id from group name. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which the info is gathered. +function WAREHOUSE:_GetInfoFromGroup(group) + + ---@param #string text The text to analyse. + local function analyse(text) + + -- Get rid of #0001 tail from spawn. + local unspawned=UTILS.Split(text, "#")[1] + + -- Split keywords. + local keywords=UTILS.Split(unspawned, "_") + + local _wid=nil -- warehouse UID + local _aid=nil -- asset UID + local _rid=nil -- request UID + + -- Loop over keys. + for _,keys in pairs(keywords) do + local str=UTILS.Split(keys, "-") + local key=str[1] + local val=str[2] + if key:find("WID") then + _wid=tonumber(val) + elseif key:find("AID") then + _aid=tonumber(val) + elseif key:find("RID") then + _rid=tonumber(val) + end + end + + return _wid,_aid,_rid + end + + local name=group:GetName() + env.info("FF Name = "..tostring(name)) + + local wid,aid,rid=analyse(name) + env.info("FF warehouse id = %s"..tostring(wid)) + env.info("FF asset id = %s"..tostring(aid)) + env.info("FF request id = %s"..tostring(rid)) + +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}. From d21cee93588ae77f75f462b1e2bdc7e9e5cf4451 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 14 Aug 2018 00:10:42 +0200 Subject: [PATCH 07/73] Warehouse v0.1.6 --- Moose Development/Moose/Core/Set.lua | 4 +- .../Moose/Functional/Warehouse.lua | 572 ++++++++++++------ 2 files changed, 394 insertions(+), 182 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 6a284ab73..da13df75d 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -3984,7 +3984,9 @@ end -- @return self function SET_AIRBASE:AddAirbase( airbase ) - self:Add( airbase:GetName(), airbase ) + if airbase then + self:Add( airbase:GetName(), airbase ) + end return self end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c2d3cde99..d819b2e1c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -177,7 +177,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.5" +WAREHOUSE.version="0.1.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -194,7 +194,11 @@ WAREHOUSE.version="0.1.5" -- TODO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. -- TODO: Handle cases with immobile units. -- TODO: How to handle multiple units in a transport group? --- DONE: Add phyical object, if destroyed asssets are gone. +-- DONE: Add phyical object +-- TODO: If warehouse is destoyed, all asssets are gone. +-- TODO: If warehosue is captured, change warehouse and assets to other coalition. +-- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? +-- TODO: Handle cargo crates. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -260,14 +264,27 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) -- Define the default spawn zone. self.spawnzone=ZONE_RADIUS:New(string.format("Spawnzone %s",warehouse:GetName()), warehouse:GetVec2(), 200) + + -- Start State. + self:SetStartState("Stopped") -- Add FSM transitions. - self:AddTransition("*", "Start", "Running") - self:AddTransition("*", "Status", "*") - self:AddTransition("*", "Request", "*") - self:AddTransition("*", "Arrived", "*") - self:AddTransition("*", "Delivered", "*") + self:AddTransition("Stopped", "Load", "Stopped") + self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("Running", "Status", "*") + self:AddTransition("Paused", "Status", "*") + self:AddTransition("*", "AddAsset", "*") + self:AddTransition("*", "AddRequest", "*") + self:AddTransition("Running", "Request", "*") + self:AddTransition("*", "Arrived", "*") + self:AddTransition("*", "Delivered", "*") + self:AddTransition("Running", "Pause", "Paused") + self:AddTransition("Paused", "Unpause", "Running") + self:AddTransition("*", "Stop", "Stopped") + self:AddTransition("*", "Save", "*") + -- Pseudo Functions + --- Triggers the FSM event "Start". Starts the warehouse. -- @function [parent=#WAREHOUSE] Start -- @param #WAREHOUSE self @@ -288,12 +305,50 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Request". Executes a request if possible. + --- Trigger the FSM event "AddAsset". Add an airplane group to the warehouse stock. + -- @function [parent=#WAREHOUSE] AddAsset + -- @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. + + --- Trigger the FSM event "AddAsset" with a delay. Add an airplane group to the warehouse stock. + -- @function [parent=#WAREHOUSE] __AddAsset + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @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. + + + --- Triggers the FSM event "AddRequest". Add a request to the warehouse queue, which is processed when possible. + -- @function [parent=#WAREHOUSE] AddRequest + -- @param #WAREHOUSE self + -- @param #WAREHOUSE warehouse The warehouse 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. + + --- Triggers the FSM event "AddRequest" with a delay. Add a request to the warehouse queue, which is processed when possible. + -- @function [parent=#WAREHOUSE] __AddRequest + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param #WAREHOUSE warehouse The warehouse 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. + + + --- Triggers the FSM event "Request". Executes a request from the queue 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. + --- Triggers the FSM event "Request" after a delay. Executes a request from the queue if possible. -- @function [parent=#WAREHOUSE] __Request -- @param #WAREHOUSE self -- @param #number Delay Delay in seconds. @@ -330,124 +385,6 @@ 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=DCSdesc.displayName - local DCScategory=DCSgroup:getCategory() - local DCStype=DCSunit:getTypeName() - local SpeedMax=group:GetSpeedMax() - local RangeMin=group:GetRange() - - 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)) - env.info(string.format("speed max = %s", tostring(SpeedMax))) - env.info(string.format("range min = %s", tostring(RangeMin))) - self:E({fullassetdesc=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.uid=self.assetid - stockitem.templatename=templategroupname - stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) - stockitem.category=DCScategory - stockitem.unittype=DCStype - stockitem.attribute=attribute - stockitem.range=RangeMin - stockitem.speedmax=SpeedMax - - -- Modify the template so that the group is spawned with the right coalition. - stockitem.template.CoalitionID=self.coalition - stockitem.template.CountryID=self.country - - table.insert(self.stock, stockitem) - end - - -- Destroy group if it is alive. - if group:IsAlive()==true then - group:Destroy() - 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 - ---- Add a request for the warehouse. --- @param #WAREHOUSE self --- @param #WAREHOUSE warehouse The warehouse 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(warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) - - nAsset=nAsset or 1 - TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED - nTransport=nTransport or 1 - Prio=Prio or 50 - - -- Increase id. - self.queueid=self.queueid+1 - - -- Request queue table item. - local request={ - uid=self.queueid, - prio=Prio, - warehouse=warehouse, - airbase=warehouse.airbase, - category=warehouse.category, - assetdesc=AssetDescriptor, - assetdescval=AssetDescriptorValue, - nasset=nAsset, - transporttype=TransportType, - ntransport=nTransport} - - -- Add request to queue. - table.insert(self.queue, request) -end - ---- Add a delayed request for the warehouse. --- @param #WAREHOUSE self --- @param #number delay Delay before the request is made in seconds. --- @param #WAREHOUSE warehouse The warehouse 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(delay, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) - SCHEDULER:New(nil,WAREHOUSE.AddRequest,{self, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio}, delay) -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. @@ -483,19 +420,55 @@ function WAREHOUSE:onafterStart(From, Event, To) self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.warehouse:GetName()) -- Handle events: - -- event takeoff - -- event landing - -- event crash/dead - -- event base captured ==> change coalition ==> add assets to other coalition - - -- Handle event that airbase was captured. - if self.airbase then - --self.airbase:HandleEvent(EVENTS.BaseCaptured, self._BaseCaptured) - end + self:HandleEvent(EVENTS.Birth, self._OnEventBirth) + self:HandleEvent(EVENTS.EngineStartup, self._OnEventEngineStartup) + self:HandleEvent(EVENTS.Takeoff, self._OnEventTakeOff) + self:HandleEvent(EVENTS.Land, self._OnEventLanding) + self:HandleEvent(EVENTS.EngineShutdown, self._OnEventEngineShutdown) + self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrDead) + self:HandleEvent(EVENTS.Dead, self._OnEventCrashOrDead) + self:HandleEvent(EVENTS.BaseCaptured, self._OnEventBaseCaptured) self:__Status(5) end +--- On after "Stop" event. Stops the warehouse, unhandles all events. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterStop(From, Event, To) + self:E(self.wid..string.format("Warehouse stopped")) + + -- Unhandle event. + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.EngineStartup) + self:UnHandleEvent(EVENTS.Takeoff) + self:UnHandleEvent(EVENTS.Land) + self:UnHandleEvent(EVENTS.EngineShutdown) + self:UnHandleEvent(EVENTS.Crash) + self:UnHandleEvent(EVENTS.Dead) + self:UnHandleEvent(EVENTS.BaseCaptured) +end + +--- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterPause(From, Event, To) + self:E(self.wid..string.format("Warehouse paused! Queued requests are not processed in this state.")) +end + +--- On after "Unpause" event. Unpauses the warehouse, i.e. requests in queue are processed again. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterUnpause(From, Event, To) + self:E(self.wid..string.format("Warehouse unpaused! Processing of requests is resumed again.")) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after Status event. Checks the queue and handles requests. @@ -555,6 +528,131 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "AddAsset" event. Add an airplane group to the warehouse stock. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @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. +function WAREHOUSE:onafterAddAsset(From, Event, To, 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=DCSdesc.displayName + local DCScategory=DCSgroup:getCategory() + local DCStype=DCSunit:getTypeName() + local SpeedMax=group:GetSpeedMax() + local RangeMin=group:GetRange() + + env.info(string.format("New asset:")) + 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)) + env.info(string.format("Speed max = %s km/h", tostring(SpeedMax))) + env.info(string.format("Range min = %s m", tostring(RangeMin))) + self:E({fullassetdesc=DCSdesc}) + + -- Get the generalized attribute. + 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.uid=self.assetid + stockitem.templatename=templategroupname + stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) + stockitem.category=DCScategory + stockitem.unittype=DCStype + stockitem.attribute=attribute + stockitem.range=RangeMin + stockitem.speedmax=SpeedMax + + -- Modify the template so that the group is spawned with the right coalition. + stockitem.template.CoalitionID=self.coalition + stockitem.template.CountryID=self.country + + table.insert(self.stock, stockitem) + end + + -- Destroy group if it is alive. + if group:IsAlive()==true then + group:Destroy() + 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 + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- On after "AddRequest" event. Add a request to the warehouse queue, which is processed when possible. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #WAREHOUSE warehouse The warehouse 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:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Prio) + + -- Defaults. + nAsset=nAsset or 1 + TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED + Prio=Prio or 50 + if nTransport==nil then + if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then + nTransport=0 + else + nTransport=1 + end + end + + -- Increase id. + self.queueid=self.queueid+1 + + -- Request queue table item. + local request={ + uid=self.queueid, + prio=Prio, + warehouse=warehouse, + airbase=warehouse.airbase, + category=warehouse.category, + assetdesc=AssetDescriptor, + assetdescval=AssetDescriptorValue, + nasset=nAsset, + transporttype=TransportType, + ntransport=nTransport, + ndelivered=0, + ntransporthome=0 + } --#WAREHOUSE.Queueitem + + -- Add request to queue. + table.insert(self.queue, request) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- On before "Request" event. Checks if the request can be fullfilled. -- @param #WAREHOUSE self -- @param #string From From state. @@ -611,6 +709,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn assets. local _spawngroups,_cargotype,_cargocategory=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP + -- Add cargo groups to request. + Request.cargogroupset=_spawngroups + Request.ndelivered=0 + -- Add groups to cargo if they don't go by themselfs. local CargoGroups --Core.Set#SET_CARGO if Request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then @@ -618,11 +720,31 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --TODO: make nearradius depended on transport type and asset type. local _loadradius=5000 local _nearradius=35 + + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + _loadradius=5000 + elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + _loadradius=500 + elseif Request.transporttype==WAREHOUSE.TransportType.APC then + _loadradius=100 + end + CargoGroups = SET_CARGO:New() - for _,_group in pairs(_spawngroups:GetSetObjects()) do - local cargogroup = CARGO_GROUP:New(_group, _cargotype, "Peter", _loadradius, _nearradius) + + for _i,_group in pairs(_spawngroups:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + local _wid,_aid,_rid=self:_GetIDsFromGroup(group) + local _alias=self:_alias(group:GetTypeName(),_wid,_aid,_rid) + local cargogroup = CARGO_GROUP:New(_group, _cargotype,_alias,_loadradius,_nearradius) CargoGroups:AddCargo(cargogroup) end + + end + + -- Self request! Assets are only spawned but not routed or transported anywhere. + if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then + env.info("FF selfrequest!") + return end ------------------------------------------------------------------------------------------------------------------------------------ @@ -661,12 +783,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end - -- Add cargo groups to request. - Request.cargogroupset=_spawngroups - Request.ndelivered=0 - - --local bla=UTILS.DeepCopy(Request) - -- Add request to pending queue. table.insert(self.pending, Request) @@ -687,7 +803,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Pickup and deploy zones/bases. local PickupAirbaseSet = SET_AIRBASE:New():AddAirbase(self.airbase) local DeployAirbaseSet = SET_AIRBASE:New():AddAirbase(Request.airbase) - local DeployZoneSet = SET_ZONE:New():AddZone(Request.airbase:GetZone()) + local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) -- Cargo dispatcher. local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER @@ -905,6 +1021,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Filter the requested cargo assets. local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + -- No assets in stock :( if #_assetstock==0 then return nil,nil,nil end @@ -919,6 +1036,14 @@ function WAREHOUSE:_SpawnAssetRequest(Request) Parking=self:_GetParkingForAssets(_assetstock) end + -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. + local UnControlled=false + local AIOnOff=true + if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then + UnControlled=true + AIOnOff=false + end + -- Create an empty set. local groupset=SET_GROUP:New():FilterDeads() @@ -940,7 +1065,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Spawn object. Spawn with ALIAS here or DCS crashes! --local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) + local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country):InitUnControlled(UnControlled):InitAIOnOff(AIOnOff) local _group=nil --Wrapper.Group#GROUP local _attribute=_assetitem.attribute @@ -1039,7 +1164,7 @@ function WAREHOUSE:onafterDelivered(From, Event, To, groupset, request) -- Put assets in new warehouse. for _,_group in pairs(groupset:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP - request.warehouse:AddAsset(group:GetTemplate(), 1) + request.warehouse:AddAsset(group:GetName(), 1) end end @@ -1092,7 +1217,7 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) -- Nil check if Template==nil then - self:E("ERROR: Template nil") + self:E(self.wid.."ERROR: Template nil in RouteAir!") return end @@ -1127,7 +1252,7 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) --[[ -- tasks local TaskCombo = {} - TaskCombo[#TaskCombo+1]=self:_SimpleTaskFunction("WAREHOUSE._Arrived2") + TaskCombo[#TaskCombo+1]=self:_SimpleTaskFunction("WAREHOUSE._ArrivedSimple") ToWaypoint.task = {} ToWaypoint.task.id = "ComboTask" @@ -1176,22 +1301,30 @@ function WAREHOUSE:_RouteTrain(Group, Coordinate, Speed) end end ---- Task function for last waypoint. Triggering the Delivered event. +--- Task function for last waypoint. Triggering the "Arrived" event. -- @param Wrapper.Group#GROUP group The group that arrived. -- @param #WAREHOUSE self function WAREHOUSE._Arrived(group, warehouse) env.info(warehouse.wid..string.format("Group %s arrived", tostring(group:GetName()))) + --Trigger delivered event. warehouse:__Arrived(1, group) + end ---- Task function for last waypoint. Triggering the Delivered event. +--- Simple task function for last waypoint. Triggering the "Arrived" event. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that arrived. -function WAREHOUSE:_Arrived2(group) - env.info(self.wid..string.format("Group %s arrived2", tostring(group:GetName()))) - --Trigger delivered event. - self:__Arrived(1, group) +function WAREHOUSE:_ArrivedSimple(group) + + if group then + --local self:_GetIDsFromGroup(group) + env.info(self.wid..string.format("Group %s arrived at warehouse ", tostring(group:GetName()))) + + --Trigger delivered event. + self:__Arrived(1, group) + end + end --- Arrived event if an air unit/group arrived at its destination. @@ -1207,6 +1340,59 @@ function WAREHOUSE:_ArrivedEvent(EventData) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event handler functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventBirth(EventData) + self:E(self.wid..string.format("Warehouse %s captured event birth!",self.warehouse:GetName())) +end + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventEngineStartup(EventData) + self:E(self.wid..string.format("Warehouse %s captured event engine startup!",self.warehouse:GetName())) +end + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventTakeOff(EventData) + self:E(self.wid..string.format("Warehouse %s captured event takeoff!",self.warehouse:GetName())) +end + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventLanding(EventData) + self:E(self.wid..string.format("Warehouse %s captured event landing!",self.warehouse:GetName())) +end + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventEngineShutdown(EventData) + self:E(self.wid..string.format("Warehouse %s captured event engine shutdown!",self.warehouse:GetName())) +end + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventCrashOrDead(EventData) + self:E(self.wid..string.format("Warehouse %s captured event birth!",self.warehouse:GetName())) +end + +--- Warehouse event handling function. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA Eventdata Event data. +function WAREHOUSE:_OnEventBaseCaptured(EventData) + self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.warehouse:GetName())) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1214,14 +1400,15 @@ end --- Checks if the request can be fulfilled in general. If not, it is removed from the queue. -- Check if departure and destination bases are of the right type. -- @param #WAREHOUSE self +-- @param #table queue The queue which is holding the requests to check. -- @param #WAREHOUSE.Queueitem qitem The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. -function WAREHOUSE:_CheckRequestValid() +function WAREHOUSE:_CheckRequestValid(queue) -- Requests to delete. local delid={} - for _,_request in pairs(self.queue) do + for _,_request in pairs(queue) do local request=_request --#WAREHOUSE.Queueitem local valid=true @@ -1400,6 +1587,7 @@ function WAREHOUSE:_CheckRequestValid() end + -- Add request as unvalid and delete it later. if not valid then table.insert(delid, request.id) end @@ -1498,20 +1686,6 @@ function WAREHOUSE:_CheckRequestNow(request) return okay end - ---- Creates a unique name for spawned assets. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Stockitem _assetitem Asset for which the name is created. --- @param #WAREHOUSE.Queueitem _queueitem (Optional) Request specific name. --- @return #string Alias name "UnitType\_WID-%02d\_AID-%04d" -function WAREHOUSE:_Alias(_assetitem,_queueitem) - local _alias=string.format("%s_WID-%02d_AID-%04d", _assetitem.unittype, self.uid,_assetitem.uid) - if _queueitem then - _alias=_alias..string.format("_RID-%04d",_queueitem.uid) - end - return _alias -end - ---Sorts the queue and checks if the request can be fullfilled. -- @param #WAREHOUSE self -- @return #WAREHOUSE.Queueitem Chosen request. @@ -1546,7 +1720,7 @@ function WAREHOUSE:_SimpleTaskFunction(Function) -- Task script. local DCSScript = {} - DCSScript[#DCSScript+1] = string.format('env.info("FF hello simple task function") ') + DCSScript[#DCSScript+1] = string.format('env.info("WAREHOUSE: Simple task function called!") ') DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:Find( ... ) ') -- The group that executes the task function. Very handy with the "...". DCSScript[#DCSScript+1] = string.format('local mystatic=STATIC:FindByName(%s) ', warehouse) -- The static that holds the warehouse self object. DCSScript[#DCSScript+1] = string.format('local warehouse = mygroup:GetState(mystatic, "WAREHOUSE") ') -- Get the warehouse self object from the static. @@ -1651,7 +1825,7 @@ end function WAREHOUSE:_GetRequestOfGroup(group, queue) -- Get warehouse, asset and request ID from group name. - local wid,aid,rid=self:_GetInfoFromGroup(group) + local wid,aid,rid=self:_GetIDsFromGroup(group) -- Find the request. for _,_request in pairs(queue) do @@ -1663,10 +1837,41 @@ function WAREHOUSE:_GetRequestOfGroup(group, queue) end ---- Get warehouse id, asset id and request id from group name. +--- Creates a unique name for spawned assets. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem _assetitem Asset for which the name is created. +-- @param #WAREHOUSE.Queueitem _queueitem (Optional) Request specific name. +-- @return #string Alias name "UnitType\_WID-%d\_AID-%d\_RID-%d" +function WAREHOUSE:_Alias(_assetitem,_queueitem) + local _alias=string.format("%s_WID-%d_AID-%d", _assetitem.unittype, self.uid,_assetitem.uid) + if _queueitem then + _alias=_alias..string.format("_RID-%d",_queueitem.uid) + end + return _alias +end + +--- Creates a unique name for spawned assets. +-- @param #WAREHOUSE self +-- @param #string unittype Type of unit. +-- @param #number wid Warehouse id. +-- @param #number aid Asset item id. +-- @param #number qid Queue/request item id. +-- @return #string Alias name "UnitType\_WID-%d\_AID-%d\_RID-%d" +function WAREHOUSE:_alias(unittype, wid, aid, qid) + local _alias=string.format("%s_WID-%d_AID-%d", unittype, wid, aid) + if qid then + _alias=_alias..string.format("_RID-%d", qid) + end + return _alias +end + +--- Get warehouse id, asset id and request id from group name (alias). -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which the info is gathered. -function WAREHOUSE:_GetInfoFromGroup(group) +-- @return #number Warehouse ID. +-- @return #number Asset ID. +-- @return #number Request ID. +function WAREHOUSE:_GetIDsFromGroup(group) ---@param #string text The text to analyse. local function analyse(text) @@ -1676,7 +1881,6 @@ function WAREHOUSE:_GetInfoFromGroup(group) -- Split keywords. local keywords=UTILS.Split(unspawned, "_") - local _wid=nil -- warehouse UID local _aid=nil -- asset UID local _rid=nil -- request UID @@ -1698,14 +1902,19 @@ function WAREHOUSE:_GetInfoFromGroup(group) return _wid,_aid,_rid end + -- Group name local name=group:GetName() - env.info("FF Name = "..tostring(name)) - + + -- Get ids local wid,aid,rid=analyse(name) - env.info("FF warehouse id = %s"..tostring(wid)) - env.info("FF asset id = %s"..tostring(aid)) - env.info("FF request id = %s"..tostring(rid)) + -- Debug info + self:E(self.wid..string.format("Group Name = %s", tostring(name))) + self:E(self.wid..string.format("Warehouse ID = %s", tostring(wid))) + self:E(self.wid..string.format("Asset ID = %s", tostring(aid))) + self:E(self.wid..string.format("Request ID = %s", tostring(rid))) + + return wid,aid,rid end --- Filter stock assets by table entry. @@ -1918,6 +2127,7 @@ function WAREHOUSE:_PrintQueue(queue, name) env.info(text) end end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- From 7e4c1d8d3d9a214f765f8acd0d07e57f75455780 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 14 Aug 2018 16:01:45 +0200 Subject: [PATCH 08/73] Warehouse 0.1.7 --- Moose Development/Moose/Core/Event.lua | 10 + .../Moose/Functional/Warehouse.lua | 253 ++++++++++++++---- 2 files changed, 212 insertions(+), 51 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index a1be00587..add9fc3d8 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -257,6 +257,10 @@ EVENTS = { -- @field DCS#Unit.Category TgtCategory (UNIT) The category of the target. -- @field #string TgtTypeName (UNIT) The type name of the target. -- +-- @field DCS#Airbase place The @{DCS#Airbase} +-- @field Wrapper.Airbase#AIRBASE Place The MOOSE airbase object. +-- @field #string PlaceName The name of the airbase. +-- -- @field weapon The weapon used during the event. -- @field Weapon -- @field WeaponName @@ -933,6 +937,12 @@ function EVENT:onEvent( Event ) Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end + + -- Place should be given for takeoff and landing events as well as base captured. It should be a DCS airbase. + if Event.place then + Event.Place=AIRBASE:Find(Event.place) + Event.PlaceName=Event.Place:GetName() + end -- @FC: something like this should be added. --[[ diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index d819b2e1c..34e90698e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -26,6 +26,7 @@ -- @field DCS#coalition.side coalition Coalition ID the warehouse belongs to. -- @field DCS#country.id country Country ID the warehouse belongs to. -- @field Wrapper.Airbase#AIRBASE airbase Airbase the warehouse belongs to. +-- @field #string airbasename Name of the airbase associated to the warehouse. -- @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.Point#COORDINATE road Closest point to warehouse on road. @@ -89,6 +90,7 @@ WAREHOUSE = { coalition = nil, country = nil, airbase = nil, + airbasename = nil, category = -1, coordinate = nil, road = nil, @@ -111,6 +113,7 @@ WAREHOUSE = { -- @field #table template The spawn template of the 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 #number nunits Number of units in the group. -- @field #number range Range of the unit in meters. -- @field #number speedmax Maximum speed in km/h the unit can do. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. @@ -127,6 +130,11 @@ WAREHOUSE = { -- @field #number nasset Number of asset groups requested. -- @field #WAREHOUSE.TransportType transporttype Transport unit type. -- @field #number ntransport Number of transport units requested. + +--- Item of the warehouse pending queue table. +-- @type WAREHOUSE.Pendingitem +-- @extends #WAREHOUSE.Queueitem +-- @field #table assetlist Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}. -- @field #number ndelivered Number of groups delivered to destination. Is managed automatically. -- @field #number ntransporthome Number of transports back home. Is managed automatically. -- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. Is managed automatically. @@ -177,7 +185,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.6" +WAREHOUSE.version="0.1.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -239,11 +247,13 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) if airbase then -- User requested. self.airbase=airbase + self.airbasename=self.airbase:GetName() else -- Closest of the same coalition but within a certain range. local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 3000 then self.airbase=_airbase + self.airbasename=self.airbase:GetName() self.category=self.airbase:GetDesc().category end end @@ -269,19 +279,21 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) self:SetStartState("Stopped") -- Add FSM transitions. - self:AddTransition("Stopped", "Load", "Stopped") - self:AddTransition("Stopped", "Start", "Running") - self:AddTransition("Running", "Status", "*") - self:AddTransition("Paused", "Status", "*") - self:AddTransition("*", "AddAsset", "*") - self:AddTransition("*", "AddRequest", "*") - self:AddTransition("Running", "Request", "*") - self:AddTransition("*", "Arrived", "*") - self:AddTransition("*", "Delivered", "*") - self:AddTransition("Running", "Pause", "Paused") - self:AddTransition("Paused", "Unpause", "Running") - self:AddTransition("*", "Stop", "Stopped") - self:AddTransition("*", "Save", "*") + self:AddTransition("Stopped", "Load", "Stopped") + self:AddTransition("Stopped", "Start", "Running") + self:AddTransition("Running", "Status", "*") + self:AddTransition("Paused", "Status", "*") + self:AddTransition("*", "AddAsset", "*") + self:AddTransition("*", "AddRequest", "*") + self:AddTransition("Running", "Request", "*") + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Arrived", "*") + self:AddTransition("*", "Delivered", "*") + self:AddTransition("*", "SelfDelivered", "*") + self:AddTransition("Running", "Pause", "Paused") + self:AddTransition("Paused", "Unpause", "Running") + self:AddTransition("*", "Stop", "Stopped") + self:AddTransition("*", "Save", "*") -- Pseudo Functions @@ -294,6 +306,34 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Stop". Stops the warehouse. + -- @function [parent=#WAREHOUSE] Stop + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Stop" after a delay. Stops the warehouse. + -- @function [parent=#WAREHOUSE] __Stop + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Pause". Pauses the warehouse. + -- @function [parent=#WAREHOUSE] Pauses + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Pause" after a delay. Pause the warehouse. + -- @function [parent=#WAREHOUSE] __Pause + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Unpause". Pauses the warehouse. + -- @function [parent=#WAREHOUSE] UnPause + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Unpause" after a delay. Pause the warehouse. + -- @function [parent=#WAREHOUSE] __Unpause + -- @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 @@ -705,13 +745,18 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) ------------------------------------------------------------------------------------------------------------------------------------ -- Cargo assets. ------------------------------------------------------------------------------------------------------------------------------------ + + -- Pending request. + local Pending=Request --#WAREHOUSE.Pendingitem -- Spawn assets. - local _spawngroups,_cargotype,_cargocategory=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP + local _spawngroups,_cargotype,_cargocategory,_cargoassets=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP -- Add cargo groups to request. - Request.cargogroupset=_spawngroups - Request.ndelivered=0 + Pending.cargogroupset=_spawngroups + Pending.cargoassets=_cargoassets + --Request.cargogroupset=_spawngroups + --Request.ndelivered=0 -- Add groups to cargo if they don't go by themselfs. local CargoGroups --Core.Set#SET_CARGO @@ -729,8 +774,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) _loadradius=100 end + -- Empty cargo group set. CargoGroups = SET_CARGO:New() + -- Add cargo groups to set. for _i,_group in pairs(_spawngroups:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP local _wid,_aid,_rid=self:_GetIDsFromGroup(group) @@ -744,6 +791,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Self request! Assets are only spawned but not routed or transported anywhere. if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then env.info("FF selfrequest!") + self:__SelfDelivered(_spawngroups) return end @@ -1050,6 +1098,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Spawn the assets. local _delid={} local _spawngroups={} + local _assets={} -- Loop over cargo requests. for i=1,Request.nasset do @@ -1095,6 +1144,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) if _group then --_spawngroups[i]=_group groupset:AddGroup(_group) + table.insert(_assets, _assetitem) table.insert(_delid,_assetitem.uid) else self:E(self.wid.."ERROR: cargo asset could not be spawned!") @@ -1107,11 +1157,42 @@ function WAREHOUSE:_SpawnAssetRequest(Request) self:_DeleteStockItem(_id) end - return groupset,_cargotype,_cargocategory + return groupset,_cargotype,_cargocategory,_assets end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On after "Unloaded" event. Triggered when a group was unloaded from the carrier. +-- @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:onafterUnloaded(From, Event, To, group) + -- Debug info. + self:E(self.wid..string.format("Cargo %s unloaded!", tostring(group:GetName()))) + group:SmokeWhite() + + -- Get max speed of group. + local speedmax=group:GetSpeedMax() + + if group:IsGround() then + if speedmax>1 then + group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(50), speedmax*0.5, AI.Task.VehicleFormation.RANK, 3) + else + -- Immobile ground unit ==> directly put it into the warehouse. + self:Arrived(group) + end + elseif group:IsAir() then + -- Not sure if air units will be allowed as cargo even though it might be possible. Best put them into warehouse immediately. + self:Arrived(group) + elseif group:IsShip() then + -- Not sure if naval units will be allowed as cargo even though it might be possible. Best put them into warehouse immediately. + self:Arrived(group) + end + +end + --- On after "Arrived" event. Triggered when a group has arrived at its destination. -- @param #WAREHOUSE self -- @param #string From From state. @@ -1124,40 +1205,58 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) group:SmokeOrange() -- Update pending request. - self:_UpdatePending(group) + local request=self:_UpdatePending(group) + + -- All cargo delivered. + if request and request.cargogroupset:Count()==0 then + self:__Delivered(5, request.cargogroupset, request) + end end --- Update the pending requests by removing assets that have arrived. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that has arrived at its destination. +-- @return #WAREHOUSE.Pendingitem The updated request from the pending queue. function WAREHOUSE:_UpdatePending(group) -- Get request from group name. local request=self:_GetRequestOfGroup(group, self.pending) - - -- Increase number of delivered assets. - request.ndelivered=request.ndelivered+1 - -- Number of alive groups. - local nalive=request.cargogroupset:Count() + -- Get the IDs for this group. In particular, we use the asset ID to figure out which group was delivered. + local wid,aid,rid=self:_GetIDsFromGroup(group) - -- TODO: Well this does not handle the case when a group that has been delivered was killed! - if request.ndelivered>=nalive then - self:__Delivered(5, request.cargogroupset, request) + if request then + + -- Loop over cargo groups. + for _,_cargogroup in pairs(request.cargogroupset) do + local cargogroup=_cargogroup --Wrapper.Group#GROUP + + -- IDs of cargo group. + local cwid,caid,crid=self:_GetIDsFromGroup(cargogroup) + + -- Remove group from cargo group set. + if caid==aid then + request.cargogroupset:Remove(cargogroup:GetName()) + request.ndelivered=request.ndelivered+1 + break + end + end + else + self:E(self.wid..string.format("ERROR: pending request could not be updated since request did not exist in pending queue!")) end + + return request end - --- On after "Delivered" event. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered. --- @param #WAREHOUSE.Queueitem request -function WAREHOUSE:onafterDelivered(From, Event, To, groupset, request) +-- @param #WAREHOUSE.Pendingitem request +function WAREHOUSE:onafterDelivered(From, Event, To, request) env.info("FF all assets delivered!") @@ -1169,6 +1268,27 @@ function WAREHOUSE:onafterDelivered(From, Event, To, groupset, request) end +--- On after "SelfDelivered" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered. +-- @param #WAREHOUSE.Pendingitem request +function WAREHOUSE:onafterSelfDelivered(From, Event, To, groupset, request) + + env.info("FF all assets delivered!") + + -- Put assets in new warehouse. + for _,_group in pairs(groupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + group:SmokeGreen() + end + + --TODO: delete pending queue item. + +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1249,27 +1369,16 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) ToWaypoint.linkUnit = nil end - --[[ - -- tasks - local TaskCombo = {} - TaskCombo[#TaskCombo+1]=self:_SimpleTaskFunction("WAREHOUSE._ArrivedSimple") - - ToWaypoint.task = {} - ToWaypoint.task.id = "ComboTask" - ToWaypoint.task.params = {} - ToWaypoint.task.params.tasks = TaskCombo - ]] - -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. Template.route.points[2] = ToWaypoint -- Respawn group at the current airbase. env.info("FF Respawn at current airbase group = "..Aircraft:GetName().." name before") - local bla=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) - env.info("FF Respawn at current airbase group = "..bla:GetName().." name after") + local newAC=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) + env.info("FF Respawn at current airbase group = "..newAC:GetName().." name after") -- Handle event engine shutdown and trigger delivered event. - bla:HandleEvent(EVENTS.EngineShutdown, self._ArrivedEvent) + newAC:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) else self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(Aircraft:GetName()), tostring(Aircraft:IsAlive()))) end @@ -1327,10 +1436,14 @@ function WAREHOUSE:_ArrivedSimple(group) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Event handler functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Arrived event if an air unit/group arrived at its destination. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data table. -function WAREHOUSE:_ArrivedEvent(EventData) +function WAREHOUSE:_OnEventArrived(EventData) local unit=EventData.IniUnit unit:SmokeBlue() @@ -1340,15 +1453,18 @@ function WAREHOUSE:_ArrivedEvent(EventData) end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Event handler functions -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA Eventdata Event data. function WAREHOUSE:_OnEventBirth(EventData) self:E(self.wid..string.format("Warehouse %s captured event birth!",self.warehouse:GetName())) + + if EventData and EventData.id==world.event.S_EVENT_BIRTH then + if EventData.IniGroup then + local group=EventData.IniGroup + + end + end end --- Warehouse event handling function. @@ -1391,6 +1507,41 @@ end -- @param Core.Event#EVENTDATA Eventdata Event data. function WAREHOUSE:_OnEventBaseCaptured(EventData) self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.warehouse:GetName())) + + -- This warehouse does not have an airbase and never had one. So i could not be captured. + if self.airbasename==nil then + return + end + + if EventData and EventData.id==world.event.S_EVENT_BASE_CAPTURED then + if EventData.Place then + + local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + + if EventData.PlaceName==self.airbasename then + -- Okay, this airbase belongs or did belong to this warehouse. + + -- New coalition of airbase after it was captured. + local coalitionAirbase=airbase:GetCoalition() + + -- what can happen? + -- warehouse is blue, airbase is blue and belongs to warehouse and red captures it. ==> self.airbase=nil + -- warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place + if self.airbase==nil then + -- Warehouse lost this airbase previously and not it was re-captured. + if coalitionAirbase == self.coalition then + self.airbase=airbase + end + else + -- Captured airbase belongs to this warehouse but was captured by other coaltion. + if coalitionAirbase ~= self.coalition then + self.airbase=nil + end + end + + end + end + end end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1821,7 +1972,7 @@ end -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which the info is gathered. -- @param #table queue Queue holding all requests. --- @return #WAREHOUSE.Queueitem The request belonging to this group. +-- @return #WAREHOUSE.Pendingitem The request belonging to this group. function WAREHOUSE:_GetRequestOfGroup(group, queue) -- Get warehouse, asset and request ID from group name. From 7a5aa3a4f94102519c02bc63b8a37d80f0183494 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 15 Aug 2018 00:13:33 +0200 Subject: [PATCH 09/73] bla --- .../Moose/Functional/Warehouse.lua | 29 ++++++++++++------- Moose Development/Moose/Wrapper/Group.lua | 27 +++++++++++------ 2 files changed, 36 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 34e90698e..b9b5ac0cb 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -2053,19 +2053,26 @@ function WAREHOUSE:_GetIDsFromGroup(group) return _wid,_aid,_rid end - -- Group name - local name=group:GetName() - - -- Get ids - local wid,aid,rid=analyse(name) + if group then - -- Debug info - self:E(self.wid..string.format("Group Name = %s", tostring(name))) - self:E(self.wid..string.format("Warehouse ID = %s", tostring(wid))) - self:E(self.wid..string.format("Asset ID = %s", tostring(aid))) - self:E(self.wid..string.format("Request ID = %s", tostring(rid))) + -- Group name + local name=group:GetName() + + -- Get ids + local wid,aid,rid=analyse(name) - return wid,aid,rid + -- Debug info + self:E(self.wid..string.format("Group Name = %s", tostring(name))) + self:E(self.wid..string.format("Warehouse ID = %s", tostring(wid))) + self:E(self.wid..string.format("Asset ID = %s", tostring(aid))) + self:E(self.wid..string.format("Request ID = %s", tostring(rid))) + + return wid,aid,rid + else + self:E("WARNING: Group not found in GetIDsFromGroup() function!") + end + + end --- Filter stock assets by table entry. diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index f78f9ef04..96b44357d 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1460,9 +1460,11 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- -- Get the units of the group. local units=self:GetUnits() - for UnitID,_unit in pairs(units) do + local x + local y + for UnitID=1,#units do - local unit=_unit --Wrapper.Unit#UNIT + local unit=units[UnitID] --Wrapper.Unit#UNIT -- 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) @@ -1472,20 +1474,27 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- -- 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 + + + SpawnTemplate.units[UnitID].x = uc.x --Parkingspot.x + SpawnTemplate.units[UnitID].y = uc.z --Parkingspot.z + SpawnTemplate.units[UnitID].alt = uc.y --Parkingspot.y SpawnTemplate.units[UnitID].parking = TermialID SpawnTemplate.units[UnitID].parking_id = nil + + if UnitID==1 then + x=uc.x + y=uc.z + end end - SpawnPoint.x = AirbaseCoord.x - SpawnPoint.y = AirbaseCoord.z + SpawnPoint.x = x --AirbaseCoord.x + SpawnPoint.y = y --AirbaseCoord.z - SpawnTemplate.x = AirbaseCoord.x - SpawnTemplate.y = AirbaseCoord.z + SpawnTemplate.x = x --AirbaseCoord.x + SpawnTemplate.y = y --AirbaseCoord.z -- Set uncontrolled state. SpawnTemplate.uncontrolled=Uncontrolled From 7599459779d19e3271ef06961ae7145c6098ac6a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 15 Aug 2018 15:58:20 +0200 Subject: [PATCH 10/73] Warehouse 0.1.7w --- .../Moose/Functional/Warehouse.lua | 183 ++++++++++++------ 1 file changed, 122 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index b9b5ac0cb..7d08c18b2 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4,9 +4,9 @@ -- 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). +-- * Manages requests of assets from other warehouses. +-- * Take care of transportation to other warehouses and its accociated airbases. +-- * Different means of automatic transportation (planes, helicopters, APCs, selfpropelled). -- -- # QUICK START GUIDE -- @@ -32,6 +32,7 @@ -- @field Core.Point#COORDINATE road Closest point to warehouse on road. -- @field Core.Point#COORDINATE rail Closest point to warehouse on rail. -- @field Core.Zone#ZONE spawnzone Zone in which assets are spawned. +-- @field Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION capturezone Zone capture object handling the capturing of the warehouse spawn zone. -- @field #string wid Identifier of the warehouse printed before other output to DCS.log file. -- @field #number uid Unit identifier of the warehouse. Derived from the associated airbase. -- @field #number markerid ID of the warehouse marker at the airbase. @@ -96,6 +97,7 @@ WAREHOUSE = { road = nil, rail = nil, spawnzone = nil, + capturezone = nil, wid = nil, uid = nil, markerid = nil, @@ -458,6 +460,23 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Debug mark spawn zone. self.spawnzone:BoundZone(60, self.country) self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.warehouse:GetName()) + + -- Create a zone capture object. + self.capturezone=ZONE_CAPTURE_COALITION:New(self.spawnzone, self.coalition) + + -- Add warehouse to zone capture object. Does this work? + self.capturezone.warehouse=self + + -- Start capturing monitoring. + self.capturezone:Start(10, 60) + + -- Handle capturing. + function self.capturezone:OnEnterCaptured() + local coalition = self:GetCoalition() + self:E(string.format("Warehouse %s was captured by coalition %d", tostring(self.warehouse:GetName()), coalition)) + self.warehouse.coalition=coalition --:SetCoalition(coalition) + self:Guard() + end -- Handle events: self:HandleEvent(EVENTS.Birth, self._OnEventBirth) @@ -469,6 +488,7 @@ function WAREHOUSE:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Dead, self._OnEventCrashOrDead) self:HandleEvent(EVENTS.BaseCaptured, self._OnEventBaseCaptured) + -- Start the status monitoring. self:__Status(5) end @@ -550,6 +570,14 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:_PrintQueue(self.queue, "Queue:") self:_PrintQueue(self.pending, "Pending:") + -- Check if requests are valid and remove invalid one. + self:_CheckRequestConsistancy(self.queue) + + -- Print queue. + self:_PrintQueue(self.queue, "Queue after consitancy:") + self:_PrintQueue(self.pending, "Pending after consistancy:") + + -- Check queue and handle requests if possible. local request=self:_CheckQueue() @@ -559,8 +587,8 @@ function WAREHOUSE:onafterStatus(From, Event, To) end -- Print queue. - self:_PrintQueue(self.queue, "Queue2:") - self:_PrintQueue(self.pending, "Pending2:") + self:_PrintQueue(self.queue, "Queue after request:") + self:_PrintQueue(self.pending, "Pending after request:") -- Call status again in 30 sec. self:__Status(30) @@ -750,13 +778,17 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local Pending=Request --#WAREHOUSE.Pendingitem -- Spawn assets. - local _spawngroups,_cargotype,_cargocategory,_cargoassets=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP + local _spawngroups,_cargoassets=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP + + -- General type and category. + local _cargotype=_cargoassets[1].attribute --#WAREHOUSE.Attribute + local _cargocategory=_cargoassets[1].category --DCS#Group.Category -- Add cargo groups to request. Pending.cargogroupset=_spawngroups Pending.cargoassets=_cargoassets - --Request.cargogroupset=_spawngroups - --Request.ndelivered=0 + Pending.cargoattribute=_cargotype + Pending.cargocategory=_cargocategory -- Add groups to cargo if they don't go by themselfs. local CargoGroups --Core.Set#SET_CARGO @@ -832,10 +864,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end -- Add request to pending queue. - table.insert(self.pending, Request) + table.insert(self.pending, Pending) -- Delete request from queue. - self:_DeleteQueueItem(Request.uid) + self:_DeleteQueueItem(Request, self.queue) -- No cargo transport necessary. return @@ -868,12 +900,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then Parking=self:_GetParkingForAssets(_assetstock) end + + -- Transport assets table. + local _transportassets={} -- 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={} + -- Spawn the transport groups. for i=1,Request.ntransport do -- Get stock item. @@ -897,13 +931,13 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) - table.insert(_delid,_assetitem.uid) + table.insert(_transportassets,_assetitem) end end -- Delete spawned items from warehouse stock. - for _,_id in pairs(_delid) do - self:_DeleteStockItem(_id) + for _,_item in pairs(_transportassets) do + self:_DeleteStockItem(_item) end -- Define dispatcher for this task. @@ -912,7 +946,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then -- Spawn the transport groups. - local _delid={} for i=1,Request.ntransport do -- Get stock item. @@ -936,15 +969,15 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) - table.insert(_delid,_assetitem.uid) + table.insert(_transportassets,_assetitem) else self:E(self.wid.."ERROR: spawngroup helo transport does not exist!") end end -- Delete spawned items from warehouse stock. - for _,_id in pairs(_delid) do - self:_DeleteStockItem(_id) + for _,_item in pairs(_transportassets) do + self:_DeleteStockItem(_item) end -- Define dispatcher for this task. @@ -957,7 +990,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif Request.transporttype==WAREHOUSE.TransportType.APC then -- Spawn the transport groups. - local _delid={} for i=1,Request.ntransport do -- Get stock item. @@ -978,13 +1010,13 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) - table.insert(_delid,_assetitem.uid) + table.insert(_transportassets,_assetitem) end end -- Delete spawned items from warehouse stock. - for _,_id in pairs(_delid) do - self:_DeleteStockItem(_id) + for _,_item in pairs(_transportassets) do + self:_DeleteStockItem(_item) end -- Define dispatcher for this task. @@ -1048,12 +1080,21 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Start dispatcher. CargoTransport:__Start(5) + + -- Add transportassets to pending queue item. + Pending.transportassets=_transportassets + + -- Add cargo groups to request. + Pending.transportgroupset=_transportgroups + Pending.transportassets=_transportassets + Pending.transportattribute=_transporttype + Pending.transportcategory=_transportcategory -- Add request to pending queue. - table.insert(self.pending, Request) + table.insert(self.pending, Pending) -- Delete request from queue. - self:_DeleteQueueItem(Request.uid) + self:_DeleteQueueItem(Request, self.queue) end @@ -1062,8 +1103,7 @@ end -- @param #WAREHOUSE self -- @param #WAREHOUSE.Queueitem Request Information table of the request. -- @return Core.Set#SET_GROUP Set of groups that were spawned. --- @return #WAREHOUSE.Attribute Generalized attribute of asset. --- @return DCS#Group.Category Category of asset, i.e. ground, air, ship, ... +-- @return #table List of spawned assets. function WAREHOUSE:_SpawnAssetRequest(Request) -- Filter the requested cargo assets. @@ -1093,10 +1133,9 @@ function WAREHOUSE:_SpawnAssetRequest(Request) end -- Create an empty set. - local groupset=SET_GROUP:New():FilterDeads() + local _groupset=SET_GROUP:New():FilterDeads() -- Spawn the assets. - local _delid={} local _spawngroups={} local _assets={} @@ -1143,9 +1182,8 @@ function WAREHOUSE:_SpawnAssetRequest(Request) if _group then --_spawngroups[i]=_group - groupset:AddGroup(_group) + _groupset:AddGroup(_group) table.insert(_assets, _assetitem) - table.insert(_delid,_assetitem.uid) else self:E(self.wid.."ERROR: cargo asset could not be spawned!") end @@ -1153,11 +1191,11 @@ function WAREHOUSE:_SpawnAssetRequest(Request) end -- Delete spawned items from warehouse stock. - for _,_id in pairs(_delid) do - self:_DeleteStockItem(_id) + for _,_item in pairs(_assets) do + self:_DeleteStockItem(_item) end - return groupset,_cargotype,_cargocategory,_assets + return _groupset,_assets end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1455,7 +1493,7 @@ end --- Warehouse event handling function. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBirth(EventData) self:E(self.wid..string.format("Warehouse %s captured event birth!",self.warehouse:GetName())) @@ -1469,42 +1507,55 @@ end --- Warehouse event handling function. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventEngineStartup(EventData) self:E(self.wid..string.format("Warehouse %s captured event engine startup!",self.warehouse:GetName())) end --- Warehouse event handling function. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventTakeOff(EventData) self:E(self.wid..string.format("Warehouse %s captured event takeoff!",self.warehouse:GetName())) end --- Warehouse event handling function. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventLanding(EventData) self:E(self.wid..string.format("Warehouse %s captured event landing!",self.warehouse:GetName())) end --- Warehouse event handling function. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventEngineShutdown(EventData) self:E(self.wid..string.format("Warehouse %s captured event engine shutdown!",self.warehouse:GetName())) end --- Warehouse event handling function. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventCrashOrDead(EventData) - self:E(self.wid..string.format("Warehouse %s captured event birth!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event dead or crash!",self.warehouse:GetName())) + + if EventData and EventData.IniUnit then + + -- Check if warehouse was destroyed. + local warehousename=self.warehouse:GetName() + if EventData.IniUnitName==warehousename then + env.info(self.wid..string.format("Warehouse %s was destroyed!", warehousename)) + --TODO: Add destroy event. + self:__Stop(1) + end + end + end --- Warehouse event handling function. +-- Handles the case when the airbase associated with the warehous is captured. -- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA Eventdata Event data. +-- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBaseCaptured(EventData) self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.warehouse:GetName())) @@ -1515,7 +1566,8 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) if EventData and EventData.id==world.event.S_EVENT_BASE_CAPTURED then if EventData.Place then - + + -- Place is the airbase that was captured. local airbase=EventData.Place --Wrapper.Airbase#AIRBASE if EventData.PlaceName==self.airbasename then @@ -1548,16 +1600,24 @@ end -- Helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Count number of troups in spawn zone of the warehouse. +-- If only enemy troops are captured. +-- @param #WAREHOUSE self +function WAREHOUSE:_CheckSpawnZone() + + --self.spawnzone:IsAllInZoneOfCoalition(Coalition) + +end + --- Checks if the request can be fulfilled in general. If not, it is removed from the queue. -- Check if departure and destination bases are of the right type. -- @param #WAREHOUSE self -- @param #table queue The queue which is holding the requests to check. --- @param #WAREHOUSE.Queueitem qitem The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. -function WAREHOUSE:_CheckRequestValid(queue) +function WAREHOUSE:_CheckRequestConsistancy(queue) -- Requests to delete. - local delid={} + local invalid={} for _,_request in pairs(queue) do local request=_request --#WAREHOUSE.Queueitem @@ -1740,15 +1800,15 @@ function WAREHOUSE:_CheckRequestValid(queue) -- Add request as unvalid and delete it later. if not valid then - table.insert(delid, request.id) + table.insert(invalid, request) end end -- loop queue items. -- Delete invalid requests. - for _,_uid in pairs(delid) do - self:_DeleteQueueItem(_uid) + for _,_request in pairs(invalid) do + self:_DeleteQueueItem(_request, self.queue) end end @@ -2235,13 +2295,13 @@ function WAREHOUSE:GetStockInfo(stock) return _data end ---- Delete item from stock. +--- Delete an asset item from stock. -- @param #WAREHOUSE self --- @param #number _uid The unique id of the item to be deleted. -function WAREHOUSE:_DeleteStockItem(_uid) +-- @param #WAREHOUSE.Stockitem stockitem Asset item to delete from stock table. +function WAREHOUSE:_DeleteStockItem(stockitem) for i=1,#self.stock do local item=self.stock[i] --#WAREHOUSE.Stockitem - if item.uid==_uid then + if item.uid==stockitem.uid then table.remove(self.stock,i) break end @@ -2250,12 +2310,13 @@ end --- Delete item from queue. -- @param #WAREHOUSE self --- @param #number _uid The id of the item to be deleted. -function WAREHOUSE:_DeleteQueueItem(_uid) - for i=1,#self.queue do - local item=self.queue[i] --#WAREHOUSE.Queueitem - if item.uid==_uid then - table.remove(self.queue,i) +-- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed. +-- @param #table queue The queue from which the item should be deleted. +function WAREHOUSE:_DeleteQueueItem(qitem, queue) + for i=1,#queue do + local _item=queue[i] --#WAREHOUSE.Queueitem + if _item.uid==qitem.uid then + table.remove(queue,i) break end end @@ -2280,8 +2341,8 @@ function WAREHOUSE:_PrintQueue(queue, name) env.info(self.wid..name) for _,_qitem in ipairs(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) + local text=self.wid..string.format("UID=%d, Prio=%d, Warehouse=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%d, Transport=%s, Ntransport=%d", + qitem.uid, qitem.prio, qitem.warehouse:GetName(), qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval),qitem.nasset,qitem.transporttype,qitem.ntransport) env.info(text) end end From 4e63bf6a224912f1dd546efc48f3d586f41b6f84 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 15 Aug 2018 15:59:09 +0200 Subject: [PATCH 11/73] Warehouse 0.1.7w --- Moose Development/Moose/Functional/Warehouse.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 7d08c18b2..9036b3cc7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1085,7 +1085,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) Pending.transportassets=_transportassets -- Add cargo groups to request. - Pending.transportgroupset=_transportgroups + Pending.transportgroupset=Transportset Pending.transportassets=_transportassets Pending.transportattribute=_transporttype Pending.transportcategory=_transportcategory From 3ce59eee3586935b9814b5729d75e2f9dcc66cf6 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 16 Aug 2018 00:11:47 +0200 Subject: [PATCH 12/73] Warehouse v0.1.8 Lots of changes and improvements. --- Moose Development/Moose/Core/Point.lua | 8 +- .../Moose/Functional/Warehouse.lua | 394 +++++++++++------- 2 files changed, 255 insertions(+), 147 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 0006d597f..ebd9237e4 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1439,7 +1439,7 @@ do -- COORDINATE --- Flares the point in a color. -- @param #COORDINATE self -- @param Utilities.Utils#FLARECOLOR FlareColor - -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:Flare( FlareColor, Azimuth ) self:F2( { FlareColor } ) trigger.action.signalFlare( self:GetVec3(), FlareColor, Azimuth and Azimuth or 0 ) @@ -1447,7 +1447,7 @@ do -- COORDINATE --- Flare the COORDINATE White. -- @param #COORDINATE self - -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareWhite( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.White, Azimuth ) @@ -1455,7 +1455,7 @@ do -- COORDINATE --- Flare the COORDINATE Yellow. -- @param #COORDINATE self - -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareYellow( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Yellow, Azimuth ) @@ -1463,7 +1463,7 @@ do -- COORDINATE --- Flare the COORDINATE Green. -- @param #COORDINATE self - -- @param DCS#Azimuth (optional) Azimuth The azimuth of the flare direction. The default azimuth is 0. + -- @param DCS#Azimuth Azimuth (optional) The azimuth of the flare direction. The default azimuth is 0. function COORDINATE:FlareGreen( Azimuth ) self:F2( Azimuth ) self:Flare( FLARECOLOR.Green, Azimuth ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 9036b3cc7..786ad6a64 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -25,6 +25,8 @@ -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. -- @field DCS#coalition.side coalition Coalition ID the warehouse belongs to. -- @field DCS#country.id country Country ID the warehouse belongs to. +-- @field #string alias Alias of the warehouse. Name its called when sending messages. +-- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coaliton. -- @field Wrapper.Airbase#AIRBASE airbase Airbase the warehouse belongs to. -- @field #string airbasename Name of the airbase associated to the warehouse. -- @field DCS#Airbase.Category category Category of the home airbase, i.e. airdrome, helipad/farp or ship. @@ -84,28 +86,30 @@ -- -- @field #WAREHOUSE WAREHOUSE = { - ClassName = "WAREHOUSE", - Debug = false, - Report = true, - warehouse = nil, - coalition = nil, - country = nil, - airbase = nil, + ClassName = "WAREHOUSE", + Debug = false, + Report = true, + warehouse = nil, + coalition = nil, + country = nil, + alias = nil, + zone = nil, + airbase = nil, airbasename = nil, - category = -1, - coordinate = nil, - road = nil, - rail = nil, - spawnzone = nil, + category = -1, + coordinate = nil, + road = nil, + rail = nil, + spawnzone = nil, capturezone = nil, - wid = nil, - uid = nil, - markerid = nil, - assetid = 0, - queueid = 0, - stock = {}, - queue = {}, - pending = {}, + wid = nil, + uid = nil, + markerid = nil, + assetid = 0, + queueid = 0, + stock = {}, + queue = {}, + pending = {}, } --- Item of the warehouse stock table. @@ -187,7 +191,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.7" +WAREHOUSE.version="0.1.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -209,6 +213,9 @@ WAREHOUSE.version="0.1.7" -- TODO: If warehosue is captured, change warehouse and assets to other coalition. -- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? -- TODO: Handle cargo crates. +-- TODO: Add general message function for sending to coaliton or debug. +-- TODO: Use RAT for routing air units. Should be possible but might need some modifications of RAT, e.g. explit spawn place. But flight plan should be better. +-- TODO: Can I make a request with specific assets? E.g., once delivered, make a request for exactly those assests that were in the original request. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -217,10 +224,11 @@ WAREHOUSE.version="0.1.7" --- WAREHOUSE constructor. Creates a new WAREHOUSE object accociated with an airbase. -- @param #WAREHOUSE self -- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. +-- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static -- @param Core.Zone#ZONE spawnzone (Optional) The zone in which units are spawned and despawned when they leave or arrive the warehouse. Default is a zone of 200 meters around the warehouse. -- @param Wrapper.Airbase#AIRBASE airbase (Optional) The airbase belonging to the warehouse. Default is the closest airbase to the warehouse structure as long as it within a range of 3 km. -- @return #WAREHOUSE self -function WAREHOUSE:New(warehouse, spawnzone, airbase) +function WAREHOUSE:New(warehouse, alias) BASE:E({warehouse=warehouse:GetName()}) -- Nil check. @@ -228,15 +236,18 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) BASE:E("ERROR: Warehouse does not exist!") return nil end + + -- Set alias. + self.alias=alias or warehouse:GetName() -- Print version. - env.info(string.format("Adding warehouse v%s for structure %s", WAREHOUSE.version, warehouse:GetName())) + env.info(string.format("Adding warehouse v%s for structure %s with alias %s", WAREHOUSE.version, warehouse:GetName(), self.alias)) -- Inherit everthing from FSM class. - local self = BASE:Inherit( self, FSM:New() ) -- #WAREHOUSE + local self = BASE:Inherit(self, FSM:New()) -- #WAREHOUSE -- Set some string id for output to DCS.log file. - self.wid=string.format("WAREHOUSE %s | ", warehouse:GetName()) + self.wid=string.format("WAREHOUSE %s | ", self.alias) -- Set some variables. self.warehouse=warehouse @@ -244,59 +255,42 @@ function WAREHOUSE:New(warehouse, spawnzone, airbase) self.coalition=warehouse:GetCoalition() self.country=warehouse:GetCountry() self.coordinate=warehouse:GetCoordinate() - - -- Set airbase. - if airbase then - -- User requested. - self.airbase=airbase + + -- Closest of the same coalition but within a certain range. + local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) + if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 3000 then + self.airbase=_airbase self.airbasename=self.airbase:GetName() - else - -- Closest of the same coalition but within a certain range. - local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) - if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 3000 then - self.airbase=_airbase - self.airbasename=self.airbase:GetName() - self.category=self.airbase:GetDesc().category - end + self.category=self.airbase:GetDesc().category end - -- Get the closest point on road and rail. - local _road=self.coordinate:GetClosestPointToRoad() - local _rail=self.coordinate:GetClosestPointToRoad(true) - - -- Set connections. - if _road and _road:Get2DDistance(self.coordinate) < 3000 then - self.road=_road - _road:MarkToAll(string.format("%s road connection.", self.warehouse:GetName()), true) - end - if _rail and _rail:Get2DDistance(self.coordinate) < 3000 then - self.rail=_rail - _rail:MarkToAll(string.format("%s rail connection.", self.warehouse:GetName()), true) - end - - -- Define the default spawn zone. - self.spawnzone=ZONE_RADIUS:New(string.format("Spawnzone %s",warehouse:GetName()), warehouse:GetVec2(), 200) + -- Define warehouse and default spawn zone. + self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s", self.warehouse:GetName()), warehouse:GetVec2(), 500) + self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 200) -- Start State. self:SetStartState("Stopped") -- Add FSM transitions. - self:AddTransition("Stopped", "Load", "Stopped") - self:AddTransition("Stopped", "Start", "Running") - self:AddTransition("Running", "Status", "*") - self:AddTransition("Paused", "Status", "*") - self:AddTransition("*", "AddAsset", "*") - self:AddTransition("*", "AddRequest", "*") - self:AddTransition("Running", "Request", "*") - self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. - self:AddTransition("*", "Arrived", "*") - self:AddTransition("*", "Delivered", "*") - self:AddTransition("*", "SelfDelivered", "*") - self:AddTransition("Running", "Pause", "Paused") - self:AddTransition("Paused", "Unpause", "Running") - self:AddTransition("*", "Stop", "Stopped") - self:AddTransition("*", "Save", "*") - + self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. + self:AddTransition("Running", "Status", "*") -- Status update in running mode. Requests are processed. + self:AddTransition("Paused", "Status", "*") -- TODO Status update in paused mode. Requests are not processed. + self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. + self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. + self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. + self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. + self:AddTransition("*", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. + self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests. + self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again. + self:AddTransition("*", "Stop", "Stopped") -- TODO Stop the warehouse. + self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. + self:AddTransition("*", "Captured", "*") -- TODO Warehouse was captured by another coalition. + self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets are gone and warehouse is stopped. + + -- Pseudo Functions --- Triggers the FSM event "Start". Starts the warehouse. @@ -436,6 +430,17 @@ function WAREHOUSE:SetSpawnZone(zone) return self end + +--- Set the airbase belonging to this warehouse. +-- Be reasonable and do not put it too far from the phyiscal warehouse structure because you troops might have a long way to get to their transports. +-- @param #WAREHOUSE self +-- @param Wrapper.Airbase#AIRBASE airbase The airbase object associated to this warehouse. +-- @return #WAREHOUSE self +function WAREHOUSE:SetAirbase(airbase) + self.airbase=airbase + return self +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -448,7 +453,7 @@ end function WAREHOUSE:onafterStart(From, Event, To) -- Short info. - local text=string.format("Starting warehouse %s:\n",self.warehouse:GetName()) + local text=string.format("Starting warehouse %s alias %s:\n",self.warehouse:GetName(), self.alias) text=text..string.format("Coaliton = %d\n", self.coalition) text=text..string.format("Country = %d\n", self.country) text=text..string.format("Airbase = %s (%s)\n", tostring(self.airbase:GetName()), tostring(self.category)) @@ -456,13 +461,38 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Save self in static object. Easier to retrieve later. self.warehouse:SetState(self.warehouse, "WAREHOUSE", self) + + -- Set airbase name and category. + if self.airbase and self.airbase:GetCoalition()==self.coalition then + self.airbasename=self.airbase:GetName() + self.category=self.airbase:GetDesc().category + else + self.airbasename=nil + self.category=-1 -- The -1 indicates that we dont have an airbase at this warehouse. + end - -- Debug mark spawn zone. - self.spawnzone:BoundZone(60, self.country) - self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.warehouse:GetName()) + -- Debug mark warehouse & spawn zone. + self.zone:BoundZone(30, self.country) + self.spawnzone:BoundZone(30, self.country) + --self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.alias) + -- Get the closest point on road and rail wrt spawnzone of ground assets. + -- TODO: Make road/rail connection input parameter. + local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad() + local _rail=self.spawnzone:GetCoordinate():GetClosestPointToRoad(true) + + -- Set connections. + if _road and _road:Get2DDistance(self.coordinate) < 3000 then + self.road=_road + _road:MarkToAll(string.format("%s road connection.", self.alias), true) + end + if _rail and _rail:Get2DDistance(self.coordinate) < 3000 then + self.rail=_rail + _rail:MarkToAll(string.format("%s rail connection.", self.alias), true) + end + -- Create a zone capture object. - self.capturezone=ZONE_CAPTURE_COALITION:New(self.spawnzone, self.coalition) + self.capturezone=ZONE_CAPTURE_COALITION:New(self.zone, self.coalition) -- Add warehouse to zone capture object. Does this work? self.capturezone.warehouse=self @@ -473,7 +503,7 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Handle capturing. function self.capturezone:OnEnterCaptured() local coalition = self:GetCoalition() - self:E(string.format("Warehouse %s was captured by coalition %d", tostring(self.warehouse:GetName()), coalition)) + self:E(string.format("Warehouse %s was captured by coalition %d!", tostring(self.alias), coalition)) self.warehouse.coalition=coalition --:SetCoalition(coalition) self:Guard() end @@ -488,6 +518,10 @@ function WAREHOUSE:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Dead, self._OnEventCrashOrDead) self:HandleEvent(EVENTS.BaseCaptured, self._OnEventBaseCaptured) + -- This event triggers the arrived event for air assets. + -- TODO Might need to make this landing or optional! + self:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) + -- Start the status monitoring. self:__Status(5) end @@ -509,6 +543,9 @@ function WAREHOUSE:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Crash) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.BaseCaptured) + + -- Stop capture zone FSM. + self.capturezone:Stop() end --- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. @@ -537,7 +574,7 @@ end -- @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 %s", self.warehouse:GetName())) + self:E(self.wid..string.format("Checking warehouse status of %s", self.alias)) -- Print queue. self:_PrintQueue(self.queue, "Queue0:") @@ -596,7 +633,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On after "AddAsset" event. Add an airplane group to the warehouse stock. +--- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -608,9 +645,12 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) -- Set default. local n=ngroups or 1 + -- Get MOOSE group. local group=GROUP:FindByName(templategroupname) - if group then + -- Check if group exists and has a DCS object. + -- TODO: Need to check this carefully if this words with CARGO etc. + if group and group:IsAlive()~=nil and group:GetDCSObject() then local DCSgroup=group:GetDCSObject() local DCSunit=DCSgroup:getUnit(1) @@ -621,7 +661,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() - env.info(string.format("New asset:")) + env.info(string.format("New asset for warehouse %s:", self.alias)) env.info(string.format("Group name = %s", group:GetName())) env.info(string.format("Display name = %s", DCSdisplay)) env.info(string.format("Category = %s", DCScategory)) @@ -637,8 +677,10 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) for i=1,n do local stockitem={} --#WAREHOUSE.Stockitem + -- Increase asset unique id counter. self.assetid=self.assetid+1 + -- Set parameters. stockitem.uid=self.assetid stockitem.templatename=templategroupname stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) @@ -649,6 +691,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) stockitem.speedmax=SpeedMax -- Modify the template so that the group is spawned with the right coalition. + -- TODO: somehow this is now acknoleged properly. Found a workaround however with SPAWN AIP functions. stockitem.template.CoalitionID=self.coalition stockitem.template.CountryID=self.country @@ -729,11 +772,10 @@ end -- @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("Warehouse %s requesting %d assets of %s=%s by transport %s", - --Request.warehouse:GetName(), Request.nasset, tostring(Request.assetdesc), tostring(Request.assetdescval), tostring(Request.transporttype))) + self:E({warehouse=self.alias, request=Request}) - -- Distance from warehouse to requesting airbase. - local distance=self.coordinate:Get2DDistance(Request.airbase:GetCoordinate()) + -- Distance from warehouse to requesting warehouse. + local distance=self.coordinate:Get2DDistance(Request.warehouse.coordinate) -- Filter the requested assets. local _assets=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) @@ -741,13 +783,20 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Check if destination is in range for all requested assets. for _,_asset in pairs(_assets) do local asset=_asset --#WAREHOUSE.Stockitem + + -- Check if destination is in range. if asset.range request denied. @@ -823,7 +872,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Self request! Assets are only spawned but not routed or transported anywhere. if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then env.info("FF selfrequest!") - self:__SelfDelivered(_spawngroups) + self:__SelfRequest(_spawngroups) return end @@ -1209,26 +1258,33 @@ end function WAREHOUSE:onafterUnloaded(From, Event, To, group) -- Debug info. self:E(self.wid..string.format("Cargo %s unloaded!", tostring(group:GetName()))) - group:SmokeWhite() + + if group and group:IsAlive() then - -- Get max speed of group. - local speedmax=group:GetSpeedMax() + -- Debug smoke. + group:SmokeWhite() - if group:IsGround() then - if speedmax>1 then - group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(50), speedmax*0.5, AI.Task.VehicleFormation.RANK, 3) - else - -- Immobile ground unit ==> directly put it into the warehouse. + -- Get max speed of group. + local speedmax=group:GetSpeedMax() + + if group:IsGround() then + if speedmax>1 then + group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(50), speedmax*0.5, AI.Task.VehicleFormation.RANK, 3) + else + -- Immobile ground unit ==> directly put it into the warehouse. + self:Arrived(group) + end + elseif group:IsAir() then + -- Not sure if air units will be allowed as cargo even though it might be possible. Best put them into warehouse immediately. self:Arrived(group) + elseif group:IsShip() then + -- Not sure if naval units will be allowed as cargo even though it might be possible. Best put them into warehouse immediately. + self:Arrived(group) end - elseif group:IsAir() then - -- Not sure if air units will be allowed as cargo even though it might be possible. Best put them into warehouse immediately. - self:Arrived(group) - elseif group:IsShip() then - -- Not sure if naval units will be allowed as cargo even though it might be possible. Best put them into warehouse immediately. - self:Arrived(group) + + else + self:E(self.wid..string.format("ERROR unloaded Cargo group is not alive!")) end - end --- On after "Arrived" event. Triggered when a group has arrived at its destination. @@ -1241,15 +1297,29 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) self:E(self.wid..string.format("Cargo %s arrived!", tostring(group:GetName()))) group:SmokeOrange() - + -- Update pending request. local request=self:_UpdatePending(group) - -- All cargo delivered. - if request and request.cargogroupset:Count()==0 then - self:__Delivered(5, request.cargogroupset, request) - end + if request then + -- Number of cargo assets still in group set. + local ncargo=request.cargogroupset:Count() + + -- Info + self:E(self.wid..string.format("Cargo %d of %d arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, request.nasset, request.warehouse.alias, ncargo)) + + -- Move asset into new warehouse. + -- TODO: need to figure out which template group name I best take. + request.warehouse:__AddAsset(3, group:GetName(), 1) + + -- All cargo delivered. + if request and ncargo==0 then + self:__Delivered(5, request) + end + + end + end --- Update the pending requests by removing assets that have arrived. @@ -1267,7 +1337,7 @@ function WAREHOUSE:_UpdatePending(group) if request then -- Loop over cargo groups. - for _,_cargogroup in pairs(request.cargogroupset) do + for _,_cargogroup in pairs(request.cargogroupset:GetSetObjects()) do local cargogroup=_cargogroup --Wrapper.Group#GROUP -- IDs of cargo group. @@ -1281,7 +1351,7 @@ function WAREHOUSE:_UpdatePending(group) end end else - self:E(self.wid..string.format("ERROR: pending request could not be updated since request did not exist in pending queue!")) + self:E(self.wid..string.format("WARNING: pending request could not be updated since request did not exist in pending queue!")) end return request @@ -1296,26 +1366,30 @@ end -- @param #WAREHOUSE.Pendingitem request function WAREHOUSE:onafterDelivered(From, Event, To, request) - env.info("FF all assets delivered!") + -- Debug info + self:E(self.wid..string.format("All assets from warehouse %s delivered to warehouse %s!", self.alias, request.warehouse.alias)) - -- Put assets in new warehouse. - for _,_group in pairs(groupset:GetSetObjects()) do - local group=_group --Wrapper.Group#GROUP - request.warehouse:AddAsset(group:GetName(), 1) + -- Fireworks! + for i=1,91 do + local color=math.random(0,3) + request.warehouse.coordinate:Flare(color, i-1) end + -- Remove pending request: + self:_DeleteQueueItem(request, self.pending) + end ---- On after "SelfDelivered" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. +--- On after "SelfRequest" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered. --- @param #WAREHOUSE.Pendingitem request -function WAREHOUSE:onafterSelfDelivered(From, Event, To, groupset, request) +-- @param #WAREHOUSE.Pendingitem request Pending self request. +function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) - env.info("FF all assets delivered!") + self:E(self.wid..string.format("Assets spawned at warehouse %s after self request!", self.alias)) -- Put assets in new warehouse. for _,_group in pairs(groupset:GetSetObjects()) do @@ -1323,7 +1397,8 @@ function WAREHOUSE:onafterSelfDelivered(From, Event, To, groupset, request) group:SmokeGreen() end - --TODO: delete pending queue item. + -- Remove pending request: + self:_DeleteQueueItem(request, self.pending) end @@ -1416,7 +1491,8 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) env.info("FF Respawn at current airbase group = "..newAC:GetName().." name after") -- Handle event engine shutdown and trigger delivered event. - newAC:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) + -- Not this did not work unless the routine would retrive the state from get/set state! + --newAC:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) else self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(Aircraft:GetName()), tostring(Aircraft:IsAlive()))) end @@ -1483,11 +1559,40 @@ end -- @param Core.Event#EVENTDATA EventData Event data table. function WAREHOUSE:_OnEventArrived(EventData) - local unit=EventData.IniUnit - unit:SmokeBlue() + if EventData and EventData.IniUnit then - local group=EventData.IniGroup - self:__Arrived(1, group) + -- Unit that arrived. + local unit=EventData.IniUnit + + -- Check if unit is alive and on the ground. Engine shutdown can also be triggered in other situations! + if unit and unit:IsAlive()==true and unit:InAir()==false then + + -- Smoke unit that arrived. + unit:SmokeBlue() + + -- Get group. + local group=EventData.IniGroup + + -- Get unique IDs from group name. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + -- If all IDs are good we can assume it is a warehouse asset. + if wid~=nil and aid~=nil and rid~=nil then + + -- Debug info. + self:E(self.wid..string.format("Air asset group %s arrived.", group:GetName())) + + -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. + -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since + -- some units might still be taxiing or whatever. Therefore, we add 10 seconds for each additional unit of the group until the first arrived event is triggered. + local nunits=#group:GetUnits() + local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. + self:__Arrived(1, group) + else + self:E(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.", tostring(wid), tostring(aid), tostring(rid))) + end + end + end end @@ -1495,7 +1600,7 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBirth(EventData) - self:E(self.wid..string.format("Warehouse %s captured event birth!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event birth!",self.alias)) if EventData and EventData.id==world.event.S_EVENT_BIRTH then if EventData.IniGroup then @@ -1509,42 +1614,42 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventEngineStartup(EventData) - self:E(self.wid..string.format("Warehouse %s captured event engine startup!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event engine startup!",self.alias)) end --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventTakeOff(EventData) - self:E(self.wid..string.format("Warehouse %s captured event takeoff!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event takeoff!",self.alias)) end --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventLanding(EventData) - self:E(self.wid..string.format("Warehouse %s captured event landing!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event landing!",self.alias)) end --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventEngineShutdown(EventData) - self:E(self.wid..string.format("Warehouse %s captured event engine shutdown!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event engine shutdown!",self.alias)) end --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventCrashOrDead(EventData) - self:E(self.wid..string.format("Warehouse %s captured event dead or crash!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event dead or crash!",self.alias)) if EventData and EventData.IniUnit then -- Check if warehouse was destroyed. local warehousename=self.warehouse:GetName() if EventData.IniUnitName==warehousename then - env.info(self.wid..string.format("Warehouse %s was destroyed!", warehousename)) + env.info(self.wid..string.format("Warehouse %s alias %s was destroyed!", warehousename, self.alias)) --TODO: Add destroy event. self:__Stop(1) end @@ -1557,7 +1662,7 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBaseCaptured(EventData) - self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.warehouse:GetName())) + self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.alias)) -- This warehouse does not have an airbase and never had one. So i could not be captured. if self.airbasename==nil then @@ -1576,9 +1681,9 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) -- New coalition of airbase after it was captured. local coalitionAirbase=airbase:GetCoalition() - -- what can happen? - -- warehouse is blue, airbase is blue and belongs to warehouse and red captures it. ==> self.airbase=nil - -- warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place + -- So what can happen? + -- Warehouse is blue, airbase is blue and belongs to warehouse and red captures it ==> self.airbase=nil + -- Warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place if self.airbase==nil then -- Warehouse lost this airbase previously and not it was re-captured. if coalitionAirbase == self.coalition then @@ -1622,6 +1727,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) for _,_request in pairs(queue) do local request=_request --#WAREHOUSE.Queueitem + -- Let's assume everything is fine. local valid=true --TODO: check that @@ -2113,6 +2219,7 @@ function WAREHOUSE:_GetIDsFromGroup(group) return _wid,_aid,_rid end + self:E({_function="getids", group=group}) if group then -- Group name @@ -2206,7 +2313,8 @@ function WAREHOUSE:_GetAttribute(groupname) if group then -- Get generalized attributes. - -- Transports: Helos, planes and APCs + -- TODO: need to work on ships and trucks and SAMs and ... + -- Also the Yak-52 for example is OTHER since it only has the attribute "Battleplanes". local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") local transporthelo=group:HasAttribute("Transport helicopters") local transportapc=group:HasAttribute("Infantry carriers") @@ -2338,12 +2446,12 @@ end -- @param #table queue Queue to print. -- @param #string name Name of the queue for info reasons. function WAREHOUSE:_PrintQueue(queue, name) - env.info(self.wid..name) + self:E(self.wid..name) for _,_qitem in ipairs(queue) do local qitem=_qitem --#WAREHOUSE.Queueitem - local text=self.wid..string.format("UID=%d, Prio=%d, Warehouse=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%d, Transport=%s, Ntransport=%d", - qitem.uid, qitem.prio, qitem.warehouse:GetName(), qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval),qitem.nasset,qitem.transporttype,qitem.ntransport) - env.info(text) + local text=self.wid..string.format("UID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%d, Transport=%s, Ntransport=%d", + qitem.uid, qitem.prio, qitem.warehouse.alias, qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval),qitem.nasset,qitem.transporttype,qitem.ntransport) + self:E(text) end end From 22da329fcaae17b9c635ec0abc2f68c534ad318c Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 16 Aug 2018 16:20:36 +0200 Subject: [PATCH 13/73] Warehouse v0.1.8w --- Moose Development/Moose/Core/SpawnStatic.lua | 10 +- Moose Development/Moose/Functional/RAT.lua | 12 +- .../Moose/Functional/Warehouse.lua | 272 +++++++++++++++--- Moose Development/Moose/Wrapper/Static.lua | 5 +- 4 files changed, 248 insertions(+), 51 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 334201e4c..a6295e7cd 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -191,19 +191,17 @@ function SPAWNSTATIC:SpawnFromPointVec2( PointVec2, Heading, NewName ) --R2.1 end ---- Creates the original @{Static} at a POINT_VEC2. +--- Respawns the original @{Static}. -- @param #SPAWNSTATIC self --- @param Core.Point#POINT_VEC2 PointVec2 The 2D coordinate where to spawn the static. --- @param #number Heading The heading of the static, which is a number in degrees from 0 to 360. --- @param #string (optional) The name of the new static. +-- @param DCS#country.id (Optional) The country ID of the static after respawning.. -- @return #SPAWNSTATIC -function SPAWNSTATIC:ReSpawn() +function SPAWNSTATIC:ReSpawn(countryid) local StaticTemplate = _DATABASE:GetStaticUnitTemplate( self.SpawnTemplatePrefix ) if StaticTemplate then - local CountryID = self.CountryID + local CountryID = countryid or self.CountryID local CountryName = _DATABASE.COUNTRY_NAME[CountryID] StaticTemplate.units = nil diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 932e8e41b..74d5db139 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -2064,8 +2064,9 @@ end -- @param #table _waypoint First waypoint to be used (for continue journey, commute, etc). -- @param Core.Point#COORDINATE _lastpos (Optional) Position where the aircraft will be spawned. -- @param #number _nrespawn Number of already performed respawn attempts (e.g. spawning on runway bug). +-- @param #table parkingdata Explicitly specify the parking spots when spawning at an airport. -- @return #number Spawn index. -function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _waypoint, _lastpos, _nrespawn) +function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _livery, _waypoint, _lastpos, _nrespawn, parkingdata) self:F({rat=RAT.id, departure=_departure, destination=_destination, takeoff=_takeoff, landing=_landing, livery=_livery, waypoint=_waypoint, lastpos=_lastpos, nrespawn=_nrespawn}) -- Set takeoff type. @@ -5103,9 +5104,10 @@ end -- @param Core.Point#COORDINATE spawnplace (Optional) Place where spawning should happen. If not present, first waypoint is taken. -- @param Wrapper.Airbase#AIRBASE departure Departure airbase or zone. -- @param #number takeoff Takeoff type. +-- @param #table parkingdata Parking data, i.e. parking spot coordinates and terminal ids for all units of the group. -- @return #boolean True if modification was successful or nil if not, e.g. when no parking space was found and spawn in air is disabled. -function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, takeoff) - self:F2({waypoints=waypoints, livery=livery, spawnplace=spawnplace, departure=departure, takeoff=takeoff}) +function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, takeoff, parkingdata) + self:F2({waypoints=waypoints, livery=livery, spawnplace=spawnplace, departure=departure, takeoff=takeoff, parking=parkingdata}) -- The 3D vector of the first waypoint, i.e. where we actually spawn the template group. local PointVec3 = COORDINATE:New(waypoints[1].x, waypoints[1].alt, waypoints[1].y) @@ -5193,6 +5195,10 @@ function RAT:_ModifySpawnTemplate(waypoints, livery, spawnplace, departure, take self:T(RAT.id..string.format("Group %s is spawned on farp/ship/runway %s.", self.alias, departure:GetName())) nfree=departure:GetFreeParkingSpotsNumber(termtype, true) spots=departure:GetFreeParkingSpotsTable(termtype, true) + elseif parkingdata~=nil then + -- Parking data explicitly set by user as input parameter. + nfree=#parkingdata + spots=parkingdata else -- Helo is spawned. if self.category==RAT.cat.heli then diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 786ad6a64..1e14e8d39 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -96,7 +96,7 @@ WAREHOUSE = { zone = nil, airbase = nil, airbasename = nil, - category = -1, + category = -1, coordinate = nil, road = nil, rail = nil, @@ -191,7 +191,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.8" +WAREHOUSE.version="0.1.8w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -204,11 +204,11 @@ WAREHOUSE.version="0.1.8" -- DONE: Switch to AI_CARGO_XXX_DISPATCHER -- DONE: Add queue. -- TODO: Write documentation. --- TODO: Put active groups into the warehouse, e.g. when they were transported to this warehouse. --- TODO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. +-- DONE: Put active groups into the warehouse, e.g. when they were transported to this warehouse. +-- NOGO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. -- TODO: Handle cases with immobile units. --- TODO: How to handle multiple units in a transport group? --- DONE: Add phyical object +-- DONE: How to handle multiple units in a transport group? <== Cargo dispatchers. +-- DONE: Add phyical object. -- TODO: If warehouse is destoyed, all asssets are gone. -- TODO: If warehosue is captured, change warehouse and assets to other coalition. -- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? @@ -221,7 +221,7 @@ WAREHOUSE.version="0.1.8" -- Constructor(s) ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- WAREHOUSE constructor. Creates a new WAREHOUSE object accociated with an airbase. +--- The WAREHOUSE constructor. Creates a new WAREHOUSE object from a static object. Parameters like the coalition and country are taken from the static object structure. -- @param #WAREHOUSE self -- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static @@ -282,11 +282,12 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. - self:AddTransition("*", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. + self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests. self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again. self:AddTransition("*", "Stop", "Stopped") -- TODO Stop the warehouse. self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. + self:AddTransition("*", "Attacked", "*") -- TODO Warehouse is under attack by enemy coalitin. self:AddTransition("*", "Captured", "*") -- TODO Warehouse was captured by another coalition. self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets are gone and warehouse is stopped. @@ -430,9 +431,18 @@ function WAREHOUSE:SetSpawnZone(zone) return self end +--- Set a warehouse zone. If this zone is captured, the warehouse and all its assets fall into the hands of the enemy. +-- @param #WAREHOUSE self +-- @param Core.Zone#ZONE zone The warehouse zone. Note that this **cannot** be a polygon zone! +-- @return #WAREHOUSE self +function WAREHOUSE:SetWarehouseZone(zone) + self.zone=zone + return self +end --- Set the airbase belonging to this warehouse. --- Be reasonable and do not put it too far from the phyiscal warehouse structure because you troops might have a long way to get to their transports. +-- Note that it has to be of the same coalition as the warehouse. +-- Also, be reasonable and do not put it too far from the phyiscal warehouse structure because you troops might have a long way to get to their transports. -- @param #WAREHOUSE self -- @param Wrapper.Airbase#AIRBASE airbase The airbase object associated to this warehouse. -- @return #WAREHOUSE self @@ -441,6 +451,51 @@ function WAREHOUSE:SetAirbase(airbase) return self end +--- Set the connection of the warehouse to the road. +-- Ground assets spawned in the warehouse spawn zone will first go to this point and from there travel on road to the requesting warehouse. +-- Note that by default the road connection is set to the closest point on road from the center of the spawn zone if it is withing 3000 meters. +-- Also note, that if the parameter "coordinate" is passed as nil, any road connection is disabled and ground assets cannot travel of be transportet on the ground. +-- @param #WAREHOUSE self +-- @param Core.Point#COORDINATE coordinate The road connection. Technically, the closest point on road from this coordinate is determined by DCS API function. So this point must not be exactly on the road. +-- @return #WAREHOUSE self +function WAREHOUSE:SetRoadConnection(coordinate) + if coordinate then + self.road=coordinate:GetClosestPointToRoad() + else + self.road=false + end + return self +end + +--- Set the connection of the warehouse to the railroad. +-- This is the place where train assets or transports will be spawned. +-- @param #WAREHOUSE self +-- @param Core.Point#COORDINATE coordinate The railroad connection. Technically, the closest point on rails from this coordinate is determined by DCS API function. So this point must not be exactly on the a railroad connection. +-- @return #WAREHOUSE self +function WAREHOUSE:SetRailConnection(coordinate) + if coordinate then + self.rail=coordinate:GetClosestPointToRoad(true) + else + self.rail=false + end + return self +end + +--- Check if the warehouse is running. +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse is running and requests are processed. +function WAREHOUSE:IsRunning() + return self:is("Running") +end + +--- Check if the warehouse is paused. In this state, requests are not processed. +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse is paused. +function WAREHOUSE:IsPaused() + return self:is("Paused") +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -474,22 +529,36 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Debug mark warehouse & spawn zone. self.zone:BoundZone(30, self.country) self.spawnzone:BoundZone(30, self.country) + --self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.alias) - -- Get the closest point on road and rail wrt spawnzone of ground assets. - -- TODO: Make road/rail connection input parameter. + -- Get the closest point on road wrt spawnzone of ground assets. local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad() - local _rail=self.spawnzone:GetCoordinate():GetClosestPointToRoad(true) - - -- Set connections. - if _road and _road:Get2DDistance(self.coordinate) < 3000 then - self.road=_road - _road:MarkToAll(string.format("%s road connection.", self.alias), true) + if _road and self.road==nil then + -- Set connection to road if distance is less than 3 km. + local _Droad=_road:Get2DDistance(self.spawnzone:GetCoordinate()) + if _Droad < 3000 then + self.road=_road + end end - if _rail and _rail:Get2DDistance(self.coordinate) < 3000 then - self.rail=_rail - _rail:MarkToAll(string.format("%s rail connection.", self.alias), true) - end + -- Mark point at road connection. + if self.road then + self.road:MarkToAll(string.format("%s road connection.", self.alias), true) + end + + -- Get the closest point on railroad wrt spawnzone of ground assets. + local _rail=self.spawnzone:GetCoordinate():GetClosestPointToRoad(true) + if _rail and self.rail==nil then + -- Set rail conection if it is less than 3 km away. + local _Drail=_rail:Get2DDistance(self.spawnzone:GetCoordinate()) + if _Drail < 3000 then + self.rail=_rail + end + end + -- Mark point at rail connection. + if self.rail then + self.rail:MarkToAll(string.format("%s rail connection.", self.alias), true) + end -- Create a zone capture object. self.capturezone=ZONE_CAPTURE_COALITION:New(self.zone, self.coalition) @@ -500,11 +569,20 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Start capturing monitoring. self.capturezone:Start(10, 60) + -- Handle attack. + function self.capturezone:OnEnterAttacked() + local coalition = self:GetCoalition() + self:E(string.format("Warehouse %s is under attack!", tostring(self.warehouse.alias))) + -- Trigger FSM Attacked event. + self.warehouse:Attacked() + end + -- Handle capturing. function self.capturezone:OnEnterCaptured() local coalition = self:GetCoalition() - self:E(string.format("Warehouse %s was captured by coalition %d!", tostring(self.alias), coalition)) + self:E(string.format("Warehouse %s was captured by coalition %d!", tostring(self.warehouse.alias), coalition)) self.warehouse.coalition=coalition --:SetCoalition(coalition) + self.warehouse:Captured(coalition) self:Guard() end @@ -520,6 +598,8 @@ function WAREHOUSE:onafterStart(From, Event, To) -- This event triggers the arrived event for air assets. -- TODO Might need to make this landing or optional! + -- In fact, it would be better if the type could be defined for only for the warehouse which receives stuff, + -- since there will be warehouses with small airbases and little space or other problems! self:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) -- Start the status monitoring. @@ -563,7 +643,7 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterUnpause(From, Event, To) - self:E(self.wid..string.format("Warehouse unpaused! Processing of requests is resumed again.")) + self:E(self.wid..string.format("Warehouse %s unpaused! Processing of requests is resumed.", self.alias)) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -615,12 +695,15 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:_PrintQueue(self.pending, "Pending after consistancy:") - -- Check queue and handle requests if possible. - local request=self:_CheckQueue() + -- If warehouse is running than requests can be processed. + if self:IsRunning() then + -- 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) + -- Execute the request. If the request is really executed, it is also deleted from the queue. + if request then + self:Request(request) + end end -- Print queue. @@ -897,7 +980,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:_RouteGround(group, ToCoordinate) elseif _cargocategory==Group.Category.AIRPLANE then env.info("FF route plane "..group:GetName()) - self:_RouteAir(group, Request.airbase) + --self:_RouteAir(group, Request.airbase) + -- TEST! + group=self:_RouteAirRat(group, Request.airbase) elseif _cargocategory==Group.Category.HELICOPTER then env.info("FF route helo "..group:GetName()) self:_RouteAir(group, Request.airbase) @@ -1134,7 +1219,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) Pending.transportassets=_transportassets -- Add cargo groups to request. - Pending.transportgroupset=Transportset + Pending.transportgroupset=TransportSet Pending.transportassets=_transportassets Pending.transportattribute=_transporttype Pending.transportcategory=_transportcategory @@ -1402,6 +1487,42 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) end +--- On after "Attacked" event. Warehouse is under attack by an another coalition. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterAttacked(From, Event, To) + self:E(self.wid..string.format("Out warehouse is under attack!")) +end + +--- On after "Captured" event. Warehouse has been captured by another coalition. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param DCS#coalition.side Coalition which captured the warehouse. +function WAREHOUSE:onafterCaptured(From, Event, To, Coalition) + self:E(self.wid..string.format("Our warehouse was captured by coalition %d!", Coalition)) + + --TODO: Need to get a way to get the correct country. + local Country + if Coalition==coalition.side.BLUE then + Country=country.id.USA + elseif Coalition==coalition.side.RED then + Country=country.id.USSR + else + Country=country.id.SWITZERLAND + end + + -- Respawn warehouse with new coalition/country. + self.warehouse:ReSpawn(Country) + self.coalition=Coalition + self.country=Country + self.airbase=nil + self.category=-1 +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1433,6 +1554,53 @@ function WAREHOUSE:_RouteGround(Group, Coordinate, Speed) end end +--- Route the airplane from one airbase another. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP Aircraft 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. +-- @return Wrapper.Group#GROUP Group that was spawned by RAT. +function WAREHOUSE:_RouteAirRat(Aircraft, ToAirbase, Speed) + + if Aircraft and Aircraft:IsAlive()~=nil then + -- Get parking data of all units. + local parkingdata={} + local units=Aircraft:GetUnits() + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + local _spot,_terminal,_distance=unit:GetCoordinate():GetClosestOccupiedParkingSpot(self.airbase) + table.insert(parkingdata, {spot=_spot, TerminalID=_terminal}) + end + + -- Create a RAT object to use its flight plan. + local rat=RAT:New(Aircraft) + + -- Init some parameters. + rat:SetDeparture(self.airbase:GetName()) + rat:SetDestination(ToAirbase:GetName()) + --rat:SetCoalitionAircraft(color) + rat:SetCountry(self.country) + rat:NoRespawn() + + -- Init spawn but do not actually spawn. + rat:Spawn(0) + --rat:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata) + + -- Destroy the original aircraft. + Aircraft:Destroy() + + -- Spawn RAT aircraft at specific parking sports. + local spawnindex=rat:_SpawnWithRoute(self.airbase:GetName(), ToAirbase:GetName(), RAT.wp.cold, nil, nil, nil, nil, nil, parkingdata) + + -- Get the group and check it's name. + local group=rat.ratcraft[spawnindex].group --Wrapper.Group#GROUP + self:E(self.wid..string.format("Spawned new RAT aircraft as group %s", group:GetName())) + + return group + end + +end + --- Route the airplane from one airbase another. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP Aircraft Airplane group to be routed. @@ -1705,15 +1873,6 @@ end -- Helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Count number of troups in spawn zone of the warehouse. --- If only enemy troops are captured. --- @param #WAREHOUSE self -function WAREHOUSE:_CheckSpawnZone() - - --self.spawnzone:IsAllInZoneOfCoalition(Coalition) - -end - --- Checks if the request can be fulfilled in general. If not, it is removed from the queue. -- Check if departure and destination bases are of the right type. -- @param #WAREHOUSE self @@ -1808,7 +1967,9 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) asset_air=asset_helo or asset_plane if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - + ------------------------------------------- + -- Case where the units go my themselves -- + ------------------------------------------- if asset_air then if asset_plane then @@ -1833,6 +1994,12 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) end + -- All aircraft need an airbase of any type as depature or destination. + if self.airbase==nil or request.airbase==nil then + self:E("ERROR: incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!") + valid=false + end + elseif asset_ground then -- No ground assets directly to or from ships. @@ -1842,6 +2009,31 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) valid=false end + if asset_train then + -- Check if there is a valid path on rail. + if self.rail and request.warehouse.rail then + local onrail=self.rail:GetPathOnRoad(request.warehouse.rail, false, true) + if onrail==nil then + self:E("ERROR: incorrect request. No valid path on rail for train assets!") + valid=false + end + else + self:E("ERROR: incorrect request. Either warehouse or requesting warehouse have no connection to rail!") + valid=false + end + else + -- Check if there is a valid path on road. + if self.road and request.warehouse.road then + local onroad=self.road:GetPathOnRoad(request.warehouse.road, false, false) + if onroad==nil then + self:E("ERROR: incorrect request. No valid path on road for ground assets!") + valid=false + end + else + self:E("ERROR: incorrect request. Either warehouse or requesting warehouse have no connection to road!") + valid=false + end + end elseif asset_naval then self:E("ERROR: incorrect request. Naval units not supported yet!") diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index a591522af..ef488443c 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -141,11 +141,12 @@ end --- Respawn the @{Wrapper.Unit} at the same location with the same properties. -- This is useful to respawn a cargo after it has been destroyed. -- @param #STATIC self -function STATIC:ReSpawn() +-- @param DCS#country.id countryid The country ID used for spawning the new static. +function STATIC:ReSpawn(countryid) local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) - SpawnStatic:ReSpawn() + SpawnStatic:ReSpawn(countryid) end From 5a2e1f01b4ceac51bcbc6d8049812324e1acf67d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 16 Aug 2018 23:10:23 +0200 Subject: [PATCH 14/73] Warehouse 0.1.9 Many bugs spotted --- Moose Development/Moose/Functional/RAT.lua | 2 +- .../Moose/Functional/Warehouse.lua | 22 ++- Moose Development/Moose/Wrapper/Airbase.lua | 178 +++++++++--------- Moose Development/Moose/Wrapper/Unit.lua | 10 +- 4 files changed, 112 insertions(+), 100 deletions(-) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 74d5db139..95faf9daf 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -2116,7 +2116,7 @@ function RAT:_SpawnWithRoute(_departure, _destination, _takeoff, _landing, _live end -- Modify the spawn template to follow the flight plan. - local successful=self:_ModifySpawnTemplate(waypoints, livery, _lastpos, departure, takeoff) + local successful=self:_ModifySpawnTemplate(waypoints, livery, _lastpos, departure, takeoff, parkingdata) if not successful then return nil end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 1e14e8d39..0ed459d58 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -191,7 +191,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.8w" +WAREHOUSE.version="0.1.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1118,7 +1118,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportSet, CargoGroups, DeployZoneSet) -- Home zone. - CargoTransport:Setairbase(self.airbase) + --CargoTransport:Setairbase(self.airbase) --CargoTransport:SetHomeZone(self.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.APC then @@ -1563,24 +1563,28 @@ end function WAREHOUSE:_RouteAirRat(Aircraft, ToAirbase, Speed) if Aircraft and Aircraft:IsAlive()~=nil then + -- Get parking data of all units. local parkingdata={} + local units=Aircraft:GetUnits() for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT local _spot,_terminal,_distance=unit:GetCoordinate():GetClosestOccupiedParkingSpot(self.airbase) - table.insert(parkingdata, {spot=_spot, TerminalID=_terminal}) + table.insert(parkingdata, {Coordinate=_spot, TerminalID=_terminal}) end + env.info("FF parking data") + self:E(parkingdata) -- Create a RAT object to use its flight plan. - local rat=RAT:New(Aircraft) + local rat=RAT:New(Aircraft:GetName()) -- Init some parameters. rat:SetDeparture(self.airbase:GetName()) rat:SetDestination(ToAirbase:GetName()) --rat:SetCoalitionAircraft(color) - rat:SetCountry(self.country) - rat:NoRespawn() + rat:SetCountry(self.country) + rat:NoRespawn() -- Init spawn but do not actually spawn. rat:Spawn(0) @@ -1590,12 +1594,16 @@ function WAREHOUSE:_RouteAirRat(Aircraft, ToAirbase, Speed) Aircraft:Destroy() -- Spawn RAT aircraft at specific parking sports. - local spawnindex=rat:_SpawnWithRoute(self.airbase:GetName(), ToAirbase:GetName(), RAT.wp.cold, nil, nil, nil, nil, nil, parkingdata) + local spawnindex=rat:_SpawnWithRoute(self.airbase:GetName(), ToAirbase:GetName(), RAT.wp.hot, nil, nil, nil, nil, nil, parkingdata) -- Get the group and check it's name. local group=rat.ratcraft[spawnindex].group --Wrapper.Group#GROUP self:E(self.wid..string.format("Spawned new RAT aircraft as group %s", group:GetName())) + group:SmokeBlue() + -- Activate group. + local bla=group:SetCommand({id='Start', params={}}) + self:E({bla=bla}) return group end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 1af9548c2..7d45a38b8 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -680,99 +680,101 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID - -- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off. - if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then - - -- DCS getParking() routine returned that spot is not free. - self:E(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC))) - - else + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then + + -- Very safe uses the DCS getParking() info to check if a spot is free. Unfortunately, the function returns free=false until the aircraft has actually taken-off. + if verysafe and (parkingspot.Free==false or parkingspot.TOAC==true) then - -- Scan a radius of 50 meters around the spot. - local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) + -- DCS getParking() routine returned that spot is not free. + self:E(string.format("%s: Parking spot id %d NOT free (or aircraft has not taken off yet). Free=%s, TOAC=%s.", airport, parkingspot.TerminalID, tostring(parkingspot.Free), tostring(parkingspot.TOAC))) - -- Loop over objects within scan radius. - local occupied=false - - -- Check all units. - for _,unit in pairs(_units) do - - local _vec3=unit:getPoint() - local _coord=COORDINATE:NewFromVec3(_vec3) - local _dist=_coord:Get2DDistance(_spot) - local _safe=_overlap(aircraft, true, unit, false,_dist) - - if markobstacles then - local l,x,y,z=_GetObjectSize(unit) - _coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) - end - - if scanunits and not _safe then - occupied=true - end - end - - -- Check all statics. - for _,static in pairs(_statics) do - local _vec3=static:getPoint() - local _coord=COORDINATE:NewFromVec3(_vec3) - local _dist=_coord:Get2DDistance(_spot) - local _safe=_overlap(aircraft, true, static, false,_dist) - - if markobstacles then - local l,x,y,z=_GetObjectSize(static) - _coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) - end - - if scanstatics and not _safe then - occupied=true - end - end - - -- Check all scenery. - for _,scenery in pairs(_sceneries) do - local _vec3=scenery:getPoint() - local _coord=COORDINATE:NewFromVec3(_vec3) - local _dist=_coord:Get2DDistance(_spot) - local _safe=_overlap(aircraft, true, scenery, false,_dist) - - if markobstacles then - local l,x,y,z=_GetObjectSize(scenery) - _coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe))) - end - - if scanscenery and not _safe then - occupied=true - end - end - - -- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot. - for _,_takenspot in pairs(validspots) do - local _dist=_takenspot.Coordinate:Get2DDistance(_spot) - local _safe=_overlap(aircraft, true, aircraft, true,_dist) - if not _safe then - occupied=true - end - end - - --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) - if occupied then - self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid)) else - self:E(string.format("%s: Parking spot id %d free.", airport, _termid)) - if nvalid<_nspots then - table.insert(validspots, {Coordinate=_spot, TerminalID=_termid}) - end - nvalid=nvalid+1 - end + + -- Scan a radius of 50 meters around the spot. + local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) - end -- loop over units - - -- We found enough spots. - if nvalid>=_nspots then - return validspots - end + -- Loop over objects within scan radius. + local occupied=false + -- Check all units. + for _,unit in pairs(_units) do + + local _vec3=unit:getPoint() + local _coord=COORDINATE:NewFromVec3(_vec3) + local _dist=_coord:Get2DDistance(_spot) + local _safe=_overlap(aircraft, true, unit, false,_dist) + + if markobstacles then + local l,x,y,z=_GetObjectSize(unit) + _coord:MarkToAll(string.format("Unit %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", unit:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) + end + + if scanunits and not _safe then + occupied=true + end + end + + -- Check all statics. + for _,static in pairs(_statics) do + local _vec3=static:getPoint() + local _coord=COORDINATE:NewFromVec3(_vec3) + local _dist=_coord:Get2DDistance(_spot) + local _safe=_overlap(aircraft, true, static, false,_dist) + + if markobstacles then + local l,x,y,z=_GetObjectSize(static) + _coord:MarkToAll(string.format("Static %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", static:getName(),x,y,z,l,_dist, _termid, tostring(_safe))) + end + + if scanstatics and not _safe then + occupied=true + end + end + + -- Check all scenery. + for _,scenery in pairs(_sceneries) do + local _vec3=scenery:getPoint() + local _coord=COORDINATE:NewFromVec3(_vec3) + local _dist=_coord:Get2DDistance(_spot) + local _safe=_overlap(aircraft, true, scenery, false,_dist) + + if markobstacles then + local l,x,y,z=_GetObjectSize(scenery) + _coord:MarkToAll(string.format("Scenery %s\nx=%.1f y=%.1f z=%.1f\nl=%.1f d=%.1f\nspot %d safe=%s", scenery:getTypeName(),x,y,z,l,_dist, _termid, tostring(_safe))) + end + + if scanscenery and not _safe then + occupied=true + end + end + + -- Now check the already given spots so that we do not put a large aircraft next to one we already assigned a nearby spot. + for _,_takenspot in pairs(validspots) do + local _dist=_takenspot.Coordinate:Get2DDistance(_spot) + local _safe=_overlap(aircraft, true, aircraft, true,_dist) + if not _safe then + occupied=true + end + end + + --_spot:MarkToAll(string.format("Parking spot %d free=%s", parkingspot.TerminalID, tostring(not occupied))) + if occupied then + self:T(string.format("%s: Parking spot id %d occupied.", airport, _termid)) + else + self:E(string.format("%s: Parking spot id %d free.", airport, _termid)) + if nvalid<_nspots then + table.insert(validspots, {Coordinate=_spot, TerminalID=_termid}) + end + nvalid=nvalid+1 + end + + end -- loop over units + + -- We found enough spots. + if nvalid>=_nspots then + return validspots + end + end -- check terminal type end -- Retrun spots we found, even if there were not enough. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 653c7bf5d..e992a3ebd 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -119,10 +119,12 @@ end -- @param DCS#Unit DCSUnit An existing DCS Unit object reference. -- @return #UNIT self function UNIT:Find( DCSUnit ) - - local UnitName = DCSUnit:getName() - local UnitFound = _DATABASE:FindUnit( UnitName ) - return UnitFound + if DCSUnit then + local UnitName = DCSUnit:getName() + local UnitFound = _DATABASE:FindUnit( UnitName ) + return UnitFound + end + return nil end --- Find a UNIT in the _DATABASE using the name of an existing DCS Unit. From 6c586d69ac2f3832933129fbd597ab47cf14bb99 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 17 Aug 2018 16:36:16 +0200 Subject: [PATCH 15/73] Warehouse 0.1.9w --- Moose Development/Moose/Core/Point.lua | 65 ++- Moose Development/Moose/Functional/RAT.lua | 36 +- .../Moose/Functional/Warehouse.lua | 438 ++++++++++++++++-- Moose Development/Moose/Utilities/Utils.lua | 69 ++- Moose Development/Moose/Wrapper/Group.lua | 17 + Moose Development/Moose/Wrapper/Unit.lua | 2 +- 6 files changed, 543 insertions(+), 84 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index ebd9237e4..d2d9884a6 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -729,6 +729,20 @@ do -- COORDINATE return nil end + --- Returns the heading from this to another coordinate. + -- @param #COORDINATE self + -- @param #COORDINATE ToCoordinate + -- @return #number Heading in degrees. + function COORDINATE:HeadingTo(ToCoordinate) + local dz=ToCoordinate.z-self.z + local dx=ToCoordinate.x-self.x + local heading=math.deg(math.atan2(dz, dx)) + if heading < 0 then + heading = 360 + heading + end + return heading + end + --- Returns the wind direction (from) and strength. -- @param #COORDINATE self -- @param height (Optional) parameter specifying the height ASL. The minimum height will be always be the land height since the wind is zero below the ground. @@ -949,21 +963,53 @@ do -- COORDINATE -- @param #COORDINATE.WaypointAction Action The route point action. -- @param DCS#Speed Speed Airspeed in km/h. Default is 500 km/h. -- @param #boolean SpeedLocked true means the speed is locked. + -- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points. + -- @param #table DCSTasks A table of DCS#Task items which are executed at the waypoint. + -- @param #string description A text description of the waypoint, which will be shown on the F10 map. -- @return #table The route point. - function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked ) + function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description ) self:F2( { AltType, Type, Action, Speed, SpeedLocked } ) - + + -- Defaults + AltType=AltType or "RADIO" + if SpeedLocked==nil then + SpeedLocked=true + end + Speed=Speed or 500 + + -- Waypoint array. local RoutePoint = {} + + -- Coordinates. RoutePoint.x = self.x RoutePoint.y = self.z + -- Altitude. RoutePoint.alt = self.y - RoutePoint.alt_type = AltType or "RADIO" - + RoutePoint.alt_type = AltType + -- Waypoint type. RoutePoint.type = Type or nil RoutePoint.action = Action or nil - - RoutePoint.speed = ( Speed and Speed / 3.6 ) or ( 500 / 3.6 ) - RoutePoint.speed_locked = true + -- Set speed/ETA. + RoutePoint.speed = Speed/3.6 + RoutePoint.speed_locked = SpeedLocked + RoutePoint.ETA=nil + RoutePoint.ETA_locked = false + -- Waypoint description. + RoutePoint.name=description + -- Airbase parameters for takeoff and landing points. + if airbase then + local AirbaseID = airbase:GetID() + local AirbaseCategory = airbase:GetDesc().category + if AirbaseCategory == Airbase.Category.SHIP or AirbaseCategory == Airbase.Category.HELIPAD then + RoutePoint.linkUnit = AirbaseID + RoutePoint.helipadId = AirbaseID + elseif AirbaseCategory == Airbase.Category.AIRDROME then + RoutePoint.airdromeId = AirbaseID + else + self:T("ERROR: Unknown airbase category in COORDINATE:WaypointAir()!") + end + end + -- ["task"] = -- { @@ -976,12 +1022,11 @@ do -- COORDINATE -- }, -- end of ["params"] -- }, -- end of ["task"] - + -- Waypoint tasks. RoutePoint.task = {} RoutePoint.task.id = "ComboTask" RoutePoint.task.params = {} - RoutePoint.task.params.tasks = {} - + RoutePoint.task.params.tasks = DCSTasks or {} return RoutePoint end diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 95faf9daf..75bfe545d 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -2459,7 +2459,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) local VxCruiseMin = math.min(VxCruiseMax*0.70, 166) -- Cruise speed (randomized). Expectation value at midpoint between min and max. - local VxCruise = self:_Random_Gaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax) + local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax) -- Climb speed 90% ov Vmax but max 720 km/h. local VxClimb = math.min(self.aircraft.Vmax*0.90, 200) @@ -2817,7 +2817,7 @@ function RAT:_SetRoute(takeoff, landing, _departure, _destination, _waypoint) end -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. - local FLcruise=self:_Random_Gaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax) + local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax) -- Overrule setting if user specified a flight level explicitly. if self.FLuser then @@ -5014,38 +5014,6 @@ function RAT:_Randomize(value, fac, lower, upper) return r end ---- Generate Gaussian pseudo-random numbers. --- @param #number x0 Expectation value of distribution. --- @param #number sigma (Optional) Standard deviation. Default 10. --- @param #number xmin (Optional) Lower cut-off value. --- @param #number xmax (Optional) Upper cut-off value. --- @return #number Gaussian random number. -function RAT:_Random_Gaussian(x0, sigma, xmin, xmax) - - -- Standard deviation. Default 10 if not given. - sigma=sigma or 10 - - local r - local gotit=false - local i=0 - while not gotit do - - -- Uniform numbers in [0,1). We need two. - local x1=math.random() - local x2=math.random() - - -- Transform to Gaussian exp(-(x-x0)²/(2*sigma²). - r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0 - - i=i+1 - if (r>=xmin and r<=xmax) or i>100 then - gotit=true - end - end - - return r - -end --- Place markers of the waypoints. Note we assume a very specific number and type of waypoints here. -- @param #RAT self diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0ed459d58..d9eea2a1f 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -140,11 +140,16 @@ WAREHOUSE = { --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem -- @extends #WAREHOUSE.Queueitem --- @field #table assetlist Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}. --- @field #number ndelivered Number of groups delivered to destination. Is managed automatically. --- @field #number ntransporthome Number of transports back home. Is managed automatically. --- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. Is managed automatically. --- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. Is managed automatically. +-- @field #table cargoassets Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}. +-- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. +-- @field #number ndelivered Number of groups delivered to destination. +-- @field #number cargoattribute Attribute of cargo assets of type @{#WAREHOUSE.Attribute}. +-- @field #number cargocategory Category of cargo assets of type @{#WAREHOUSE.Category}. +-- @field #table transportassets Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}. +-- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. +-- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}. +-- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. +-- @field #number ntransporthome Number of transports back home. transportattribute --- Descriptors enumerator describing the type of the asset in stock. -- @type WAREHOUSE.Descriptor @@ -191,7 +196,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.9" +WAREHOUSE.version="0.1.9w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -272,24 +277,25 @@ function WAREHOUSE:New(warehouse, alias) self:SetStartState("Stopped") -- Add FSM transitions. - self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. - self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. - self:AddTransition("Running", "Status", "*") -- Status update in running mode. Requests are processed. - self:AddTransition("Paused", "Status", "*") -- TODO Status update in paused mode. Requests are not processed. - self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. - self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. - self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. - self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. - self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. - self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. - self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests. - self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again. - self:AddTransition("*", "Stop", "Stopped") -- TODO Stop the warehouse. - self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. - self:AddTransition("*", "Attacked", "*") -- TODO Warehouse is under attack by enemy coalitin. - self:AddTransition("*", "Captured", "*") -- TODO Warehouse was captured by another coalition. - self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets are gone and warehouse is stopped. + self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. + self:AddTransition("Running", "Status", "*") -- Status update in running mode. Requests are processed. + self:AddTransition("Paused", "Status", "*") -- TODO Status update in paused mode. Requests are not processed. + self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. + self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. + self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. + self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. + self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. + self:AddTransition("Running", "Pause", "Paused") -- TODO Pause the processing of new requests. Still possible to add assets and requests. + self:AddTransition("Paused", "Unpause", "Running") -- TODO Unpause the warehouse. Queued requests are processed again. + self:AddTransition("*", "Stop", "Stopped") -- TODO Stop the warehouse. + self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. + self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. + self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! + self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. + self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. -- Pseudo Functions @@ -495,6 +501,13 @@ function WAREHOUSE:IsPaused() return self:is("Paused") end +--- Check if the warehouse is under attack by another coalition. +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse is attacked. +function WAREHOUSE:IsAttacked() + return self:is("Attacked") +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states @@ -1492,8 +1505,21 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function WAREHOUSE:onafterAttacked(From, Event, To) +-- @param DCS#coalition.side Coalition which is attacking the warehouse. +-- @param DCS#country.id Country which is attacking the warehouse. +function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) self:E(self.wid..string.format("Out warehouse is under attack!")) + --TODO: Spawn all ground units in the spawnzone? +end + +--- On after "Defeated" event. Warehouse defeated an attack by another coalition. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterDefeated(From, Event, To) + self:E(self.wid..string.format("Attack was defeated!")) + --TODO Put all ground assets back in stock? How to remember which? Request id. Don't delete from pending? end --- On after "Captured" event. Warehouse has been captured by another coalition. @@ -1502,25 +1528,17 @@ end -- @param #string Event Event. -- @param #string To To state. -- @param DCS#coalition.side Coalition which captured the warehouse. -function WAREHOUSE:onafterCaptured(From, Event, To, Coalition) +-- @param DCS#country.id Country which has captured the warehouse. +function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) self:E(self.wid..string.format("Our warehouse was captured by coalition %d!", Coalition)) - --TODO: Need to get a way to get the correct country. - local Country - if Coalition==coalition.side.BLUE then - Country=country.id.USA - elseif Coalition==coalition.side.RED then - Country=country.id.USSR - else - Country=country.id.SWITZERLAND - end - -- Respawn warehouse with new coalition/country. self.warehouse:ReSpawn(Country) self.coalition=Coalition self.country=Country self.airbase=nil self.category=-1 + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1540,6 +1558,8 @@ function WAREHOUSE:_RouteGround(Group, Coordinate, Speed) local _speed=Speed or Group:GetSpeedMax()*0.6 -- Create task. + -- TODO: It might be necessary to ALWAYS route the group to the road connection first. + -- At the moment, the random spawn point might give another first road point which could also be a dead end like in Kobuliti(?). local Waypoints, canroad = Group:TaskGroundOnRoad(Coordinate, _speed, "Off Road", true) -- Task function triggering the arrived event. @@ -1630,6 +1650,9 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) return end + local Waypoints,Coordinates=self:_GetFlightplan(Aircraft,self.airbase,ToAirbase) + + --[[ -- Waypoints of the route. local Points={} @@ -1660,7 +1683,11 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. Template.route.points[2] = ToWaypoint - + ]] + + -- Set waypoints. + Template.route.points=Waypoints + -- Respawn group at the current airbase. env.info("FF Respawn at current airbase group = "..Aircraft:GetName().." name before") local newAC=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) @@ -1881,8 +1908,100 @@ end -- Helper functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Checks if the warehouse zone was conquered by antoher coalition. +-- @param #WAREHOUSE self +function WAREHOUSE:_CheckConquered() + + local coord=self.zone:GetCoordinate() + local radius=self.zone:GetRadius() + + -- Scan units in zone. + --TODO: need to check if scan radius does what it should! + local gotunits,_,_,units,_,_=coord:ScanObjects(radius, true, false, false) + + local Nblue=0 + local Nred=0 + local Nneutral=0 + + local CountryBlue=nil + local CountryRed=nil + local CountryNeutral=nil + + if gotunits then + -- Loop over all units. + for _,_unit in pairs(units) do + local unit=_unit --Wrapper.Unit#UNIT + + -- Get coalition and country. + local _coalition=unit:GetCoalition() + local _country=unit:GetCountry() + + if _coalition==coalition.side.BLUE then + Nblue=Nblue+1 + CountryBlue=_country + elseif _coalition==coalition.side.RED then + Nred=Nred+1 + CountryRed=_country + else + Nneutral=Nneutral+1 + CountryNeutral=_country + end + + end + end + + + -- Figure out the new coalition if any. + -- Condition is that only units of one coalition are within the zone. + local newcoalition=self.coalition + local newcountry=self.country + if Nblue>0 and Nred==0 and Nneutral==0 then + -- Only blue units in zone ==> Zone goes to blue. + newcoalition=coalition.side.BLUE + newcountry=CountryBlue + elseif Nblue==0 and Nred>0 and Nneutral==0 then + -- Only red units in zone ==> Zone goes to red. + newcoalition=coalition.side.RED + newcountry=CountryRed + elseif Nblue==0 and Nred==0 and Nneutral>0 then + -- Only neutral units in zone but neutrals do not attack or even capture! + --newcoalition=coalition.side.NEUTRAL + newcountry=CountryNeutral + end + + -- Coalition has changed ==> warehouse was captured! + if self:IsAttacked() and newcoalition ~= self.coalition then + self:Captured(newcoalition, newcountry) + end + + -- Before a warehouse can be captured, it has to be attacked. + -- That is, even if only enemy units are present it is not immediately captured in order to spawn all ground assets for defence. + if self.coalition==coalition.side.BLUE then + -- Blue warehouse is running and we have red units in the zone. + if self:IsRunning() and Nred>0 then + self:__Attacked(coalition.side.RED, CountryRed) + end + -- Blue warehouse was under attack by blue but no more blue units in zone. + if self:IsAttacked() and Nred==0 then + self:Defeated() + end + elseif self.coalition==coalition.side.RED then + -- Red Warehouse is running and we have blue units in the zone. + if self:IsRunning() and Nblue>0 then + self:__Attacked(coalition.side.BLUE, CountryBlue) + end + -- Red warehouse was under attack by blue but no more blue units in zone. + if self:IsAttacked() and Nblue==0 then + self:Defeated() + end + elseif self.coalition==coalition.side.NEUTRAL then + -- Neutrals dont attack! + end + +end + --- Checks if the request can be fulfilled in general. If not, it is removed from the queue. --- Check if departure and destination bases are of the right type. +-- Check if departure and destination bases are of the right type. -- @param #WAREHOUSE self -- @param #table queue The queue which is holding the requests to check. -- @return #boolean If true, request can be executed. If false, something is not right. @@ -2655,6 +2774,249 @@ function WAREHOUSE:_PrintQueue(queue, name) end end + + +--- Make a flight plan from a departure to a destination airport. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group +-- @param Wrapper.Airbase#AIRBASE _departure Departure airbase. +-- @param Wrapper.Airbase#AIRBASE _destination Destination airbase. +-- @return #table Table of flightplan waypoints. +-- @return #table Table of flightplan coordinates. +function WAREHOUSE:_GetFlightplan(group,_departure,_destination) + + -- Group parameters. + local Vmax=group:GetSpeedMax()/3.6 + local Range=group:GetRange() + local _category=group:GetCategory() + local DCSDesc=group:GetDCSDesc() + local ceiling=DCSDesc.Hmax + local Vymax=DCSDesc.VyMax + + -- Max cruise speed 90% of max speed. + local VxCruiseMax=0.90*Vmax + + -- Min cruise speed 70% of max cruise or 600 km/h whichever is lower. + local VxCruiseMin = math.min(VxCruiseMax*0.70, 166) + + -- Cruise speed (randomized). Expectation value at midpoint between min and max. + local VxCruise = UTILS.RandomGaussian((VxCruiseMax-VxCruiseMin)/2+VxCruiseMin, (VxCruiseMax-VxCruiseMax)/4, VxCruiseMin, VxCruiseMax) + + -- Climb speed 90% ov Vmax but max 720 km/h. + local VxClimb = math.min(Vmax*0.90, 200) + + -- Descent speed 60% of Vmax but max 500 km/h. + local VxDescent = math.min(Vmax*0.60, 140) + + -- Holding speed is 90% of descent speed. + local VxHolding = VxDescent*0.9 + + -- Final leg is 90% of holding speed. + local VxFinal = VxHolding*0.9 + + -- Reasonably civil climb speed Vy=1500 ft/min = 7.6 m/s but max aircraft specific climb rate. + local VyClimb=math.min(7.6, Vymax) + + -- Climb angle in rad. + local AlphaClimb=math.asin(VyClimb/VxClimb) + + -- Descent angle in rad. Moderate 4 degrees. + local AlphaDescent=math.rad(4) + + -- Expected cruise level (peak of Gaussian distribution) + local FLcruise_expect=200*RAT.unit.FL2m + + --- DEPARTURE AIRPORT + + -- Coordinates of departure point. + local Pdeparture=_departure:GetCoordinate() + + -- Height ASL of departure point. + local H_departure=Pdeparture.y + + --- DESTINATION AIRPORT + + -- Position of destination airport. + local Pdestination=_destination:GetCoordinate() + + -- Height ASL of destination airport/zone. + local H_destination=Pdestination.y + + --- DESCENT/HOLDING POINT + + -- Get a random point between 5 and 20 km away from the destination. + local Rhmin=8000 + local Rhmax=20000 + if _category==Group.Category.HELICOPTER then + -- For helos we set a distance between 500 to 1000 m. + Rhmin=500 + Rhmax=1000 + end + + -- Coordinates of the holding point. y is the land height at that point. + local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin) + local Pholding=COORDINATE:NewFromVec2(Vholding) + + -- AGL height of holding point. + local H_holding=Pholding.y + + -- Holding point altitude. For planes between 1600 and 2400 m AGL. For helos 160 to 240 m AGL. + local h_holding=1200 + if _category==Group.Category.HELICOPTER then + h_holding=150 + end + h_holding=UTILS.Randomize(h_holding, 0.2) + + -- This is the actual height ASL of the holding point we want to fly to + local Hh_holding=H_holding+h_holding + + -- Distance from holding point to final destination. + local d_holding=Pholding:Get2DDistance(Pdestination) + + -- GENERAL + local heading=Pdeparture:HeadingTo(Pdestination) + local d_total=Pdeparture:Get2DDistance(Pholding) + + -------------------------------------------- + + -- Height difference between departure and destination. + local deltaH=math.abs(H_departure-Hh_holding) + + -- Slope between departure and destination. + local phi = math.atan(deltaH/d_total) + + -- Adjusted climb/descent angles. + local phi_climb + local phi_descent + if (H_departure > Hh_holding) then + phi_climb=AlphaClimb+phi + phi_descent=AlphaDescent-phi + else + phi_climb=AlphaClimb-phi + phi_descent=AlphaDescent+phi + end + + -- Total distance including slope. + local D_total=math.sqrt(deltaH*deltaH+d_total*d_total) + + -- SSA triangle for sloped case. + local gamma=math.rad(180)-phi_climb-phi_descent + local a = D_total*math.sin(phi_climb)/math.sin(gamma) + local b = D_total*math.sin(phi_descent)/math.sin(gamma) + local hphi_max = b*math.sin(phi_climb) + local hphi_max2 = a*math.sin(phi_descent) + + -- Height of triangle. + local h_max1 = b*math.sin(AlphaClimb) + local h_max2 = a*math.sin(AlphaDescent) + + -- Max height relative to departure or destination. + local h_max + if (H_departure > Hh_holding) then + h_max=math.min(h_max1, h_max2) + else + h_max=math.max(h_max1, h_max2) + end + + -- Max flight level aircraft can reach for given angles and distance. + local FLmax = h_max+H_departure + + --CRUISE + -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. + local FLmin=math.max(H_departure, Hh_holding) + + -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. + if _category==Group.Category.HELICOPTER then + FLmin=math.max(H_departure, H_destination)+50 + FLmax=math.max(H_departure, H_destination)+1000 + end + + -- Ensure that FLmax not above its service ceiling. + FLmax=math.min(FLmax, ceiling) + + -- If the route is very short we set FLmin a bit lower than FLmax. + if FLmin>FLmax then + FLmin=FLmax + end + + -- Expected cruise altitude - peak of gaussian distribution. + if FLcruise_expectFLmax then + FLcruise_expect=FLmax + end + + -- Set cruise altitude. Selected from Gaussian distribution but limited to FLmin and FLmax. + local FLcruise=UTILS.RandomGaussian(FLcruise_expect, math.abs(FLmax-FLmin)/4, FLmin, FLmax) + + -- Climb and descent heights. + local h_climb = FLcruise - H_departure + local h_descent = FLcruise - Hh_holding + + -- Distances. + local d_climb = h_climb/math.tan(AlphaClimb) + local d_descent = h_descent/math.tan(AlphaDescent) + local d_cruise = d_total-d_climb-d_descent + + -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. + if d_cruise<0 then + d_cruise=100 + end + + -- Waypoints and coordinates + local wp={} + local c={} + + --- Departure/Take-off + c[#c+1]=Pdeparture + wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true,_departure, nil, "Departure") + --wp[#wp+1]=self:_Waypoint(#wp+1, "Departure", takeoff, c[#wp+1], VxClimb, H_departure, departure) + + --- Climb + local Pclimb=Pdeparture:Translate(d_climb/2, heading) + Pclimb.y=H_departure+(FLcruise-H_departure)/2 + c[#c+1]=Pclimb + wp[#wp+1]=Pclimb:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxClimb, true, nil, nil, "Climb") + --wp[#wp+1]=self:_Waypoint(#wp+1, "Climb", RAT.wp.climb, c[#wp+1], VxClimb, ) + + --- Begin of Cruise + local Pcruise1=Pclimb:Translate(d_climb/2, heading) + Pcruise1.y=FLcruise + c[#c+1]=Pcruise1 + wp[#wp+1]=Pcruise1:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Begin of Cruise") + --wp[#wp+1]=self:_Waypoint(#wp+1, "Begin of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + + --- End of Cruise + local Pcruise2=Pcruise1:Translate(d_cruise, heading) + Pcruise2.y=FLcruise + c[#c+1]=Pcruise2 + wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "End of Cruise") + --wp[#wp+1]=self:_Waypoint(#wp+1, "End of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) + + --- Descent + local Pdescent=Pcruise2:Translate(d_descent/2, heading) + Pdescent.y=FLcruise-(FLcruise-(h_holding+H_holding))/2 + c[#c+1]=Pdescent + wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent") + --wp[#wp+1]=self:_Waypoint(#wp+1, "Descent", RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + + --- Holding point + Pholding.y=H_holding+h_holding + c[#c+1]=Pholding + wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding") + --wp[#wp+1]=self:_Waypoint(#wp+1, "Holding Point", RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding) + + --- Final destination. + c[#c+1]=Pdestination + wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, nil, nil, "Final Destination") + --wp[#wp+1]=self:_Waypoint(#wp+1, "Final Destination", landing, c[#wp+1], VxFinal, H_destination, destination) + + return wp,c +end + + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 3ac8ca74e..1ccee1d39 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -591,4 +591,71 @@ function UTILS.DisplayMissionTime(duration) local local_time=UTILS.SecondsToClock(Tnow) local text=string.format("Time: %s - %02d:%02d", local_time, mission_time_minutes, mission_time_seconds) MESSAGE:New(text, duration):ToAll() -end \ No newline at end of file +end + + +--- Generate a Gaussian pseudo-random number. +-- @param #number x0 Expectation value of distribution. +-- @param #number sigma (Optional) Standard deviation. Default 10. +-- @param #number xmin (Optional) Lower cut-off value. +-- @param #number xmax (Optional) Upper cut-off value. +-- @param #number imax (Optional) Max number of tries to get a value between xmin and xmax (if specified). Default 100. +-- @return #number Gaussian random number. +function UTILS.RandomGaussian(x0, sigma, xmin, xmax, imax) + + -- Standard deviation. Default 10 if not given. + sigma=sigma or 10 + + -- Max attempts. + imax=imax or 100 + + local r + local gotit=false + local i=0 + while not gotit do + + -- Uniform numbers in [0,1). We need two. + local x1=math.random() + local x2=math.random() + + -- Transform to Gaussian exp(-(x-x0)²/(2*sigma²). + r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0 + + i=i+1 + if (r>=xmin and r<=xmax) or i>imax then + gotit=true + end + end + + return r +end + +--- Randomize a value by a certain amount. +-- @param #number value The value which should be randomized +-- @param #number fac Randomization factor. +-- @param #number lower (Optional) Lower limit of the returned value. +-- @param #number upper (Optional) Upper limit of the returned value. +-- @return #number Randomized value. +-- @usage UTILS.Randomize(100, 0.1) returns a value between 90 and 110, i.e. a plus/minus ten percent variation. +-- @usage UTILS.Randomize(100, 0.5, nil, 120) returns a value between 50 and 120, i.e. a plus/minus fivty percent variation with upper bound 120. +function UTILS.Randomize(value, fac, lower, upper) + local min + if lower then + min=math.max(value-value*fac, lower) + else + min=value-value*fac + end + local max + if upper then + max=math.min(value+value*fac, upper) + else + max=value+value*fac + end + + local r=math.random(min, max) + + return r +end + + + diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 96b44357d..10174dd5b 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1620,6 +1620,23 @@ function GROUP:InAir() return nil end +--- Returns the DCS descriptor table of the nth unit of the group. +-- @param #GROUP self +-- @param #number n (Optional) The number of the unit for which the dscriptor is returned. +-- @return DCS#Object.Desc The descriptor of the first unit of the group or #nil if the group does not exist any more. +function GROUP:GetDCSDesc(n) + -- Default. + n=n or 1 + + local unit=self:GetUnit(n) + if unit and unit:IsAlive()~=nil then + local desc=unit:GetDesc() + return desc + end + + return nil +end + do -- Route methods --- (AIR) Return the Group to an @{Wrapper.Airbase#AIRBASE}. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index e992a3ebd..303bb1fdd 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -428,7 +428,7 @@ function UNIT:GetRange() local Desc = self:GetDesc() if Desc then - local Range = Desc.range --This is in nautical miles for some reason. + local Range = Desc.range --This is in nautical miles for some reason. But should check again! if Range then Range=UTILS.NMToMeters(Range) else From 76ce28cdccb26af19cdfb79760b4a7e5af65e7d2 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sat, 18 Aug 2018 01:29:19 +0200 Subject: [PATCH 16/73] Warehouse 0.20 Capture update --- Moose Development/Moose/Core/Point.lua | 6 +- .../Moose/Functional/Warehouse.lua | 322 +++++++++++------- 2 files changed, 195 insertions(+), 133 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index d2d9884a6..77aedec5c 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -397,7 +397,7 @@ do -- COORDINATE local gotunits=false local gotscenery=false - local function EvaluateZone( ZoneObject ) + local function EvaluateZone(ZoneObject) if ZoneObject then @@ -408,7 +408,7 @@ do -- COORDINATE --if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist() and ZoneObject:isActive()) then if (ObjectCategory == Object.Category.UNIT and ZoneObject:isExist()) then - table.insert(Units, ZoneObject) + table.insert(Units, UNIT:Find(ZoneObject)) gotunits=true elseif (ObjectCategory == Object.Category.STATIC and ZoneObject:isExist()) then @@ -432,7 +432,7 @@ do -- COORDINATE world.searchObjects(scanobjects, SphereSearch, EvaluateZone) for _,unit in pairs(Units) do - self:T(string.format("Scan found unit %s", unit:getName())) + self:T(string.format("Scan found unit %s", unit:GetName())) end for _,static in pairs(Statics) do self:T(string.format("Scan found static %s", static:getName())) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index d9eea2a1f..ac3aa3cff 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -34,10 +34,10 @@ -- @field Core.Point#COORDINATE road Closest point to warehouse on road. -- @field Core.Point#COORDINATE rail Closest point to warehouse on rail. -- @field Core.Zone#ZONE spawnzone Zone in which assets are spawned. --- @field Functional.ZoneCaptureCoalition#ZONE_CAPTURE_COALITION capturezone Zone capture object handling the capturing of the warehouse spawn zone. -- @field #string wid Identifier of the warehouse printed before other output to DCS.log file. -- @field #number uid Unit identifier of the warehouse. Derived from the associated airbase. -- @field #number markerid ID of the warehouse marker at the airbase. +-- @field #number dTstatus Time interval in seconds of updating the warehouse status and processing new events. Default 30 seconds. -- @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}. @@ -88,28 +88,28 @@ WAREHOUSE = { ClassName = "WAREHOUSE", Debug = false, - Report = true, - warehouse = nil, - coalition = nil, - country = nil, - alias = nil, - zone = nil, - airbase = nil, - airbasename = nil, - category = -1, - coordinate = nil, - road = nil, - rail = nil, - spawnzone = nil, - capturezone = nil, - wid = nil, - uid = nil, - markerid = nil, - assetid = 0, - queueid = 0, - stock = {}, - queue = {}, - pending = {}, + Report = true, + warehouse = nil, + coalition = nil, + country = nil, + alias = nil, + zone = nil, + airbase = nil, + airbasename = nil, + category = -1, + coordinate = nil, + road = nil, + rail = nil, + spawnzone = nil, + wid = nil, + uid = nil, + markerid = nil, + dTstatus = 30, + assetid = 0, + queueid = 0, + stock = {}, + queue = {}, + pending = {}, } --- Item of the warehouse stock table. @@ -196,7 +196,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.1.9w" +WAREHOUSE.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -230,8 +230,6 @@ WAREHOUSE.version="0.1.9w" -- @param #WAREHOUSE self -- @param Wrapper.Static#STATIC warehouse The physical structure of the warehouse. -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static --- @param Core.Zone#ZONE spawnzone (Optional) The zone in which units are spawned and despawned when they leave or arrive the warehouse. Default is a zone of 200 meters around the warehouse. --- @param Wrapper.Airbase#AIRBASE airbase (Optional) The airbase belonging to the warehouse. Default is the closest airbase to the warehouse structure as long as it within a range of 3 km. -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) BASE:E({warehouse=warehouse:GetName()}) @@ -279,15 +277,16 @@ function WAREHOUSE:New(warehouse, alias) -- Add FSM transitions. self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. - self:AddTransition("Running", "Status", "*") -- Status update in running mode. Requests are processed. - self:AddTransition("Paused", "Status", "*") -- TODO Status update in paused mode. Requests are not processed. + self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. + self:AddTransition("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("*", "Stop", "Stopped") -- TODO Stop the warehouse. @@ -428,6 +427,15 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Set interval of status updates +-- @param #WAREHOUSE self +-- @param #number timeinterval Time interval in seconds. +-- @return #WAREHOUSE self +function WAREHOUSE:SetSpawnZone(timeinterval) + self.dTstatus=timeinterval + 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. @@ -572,32 +580,6 @@ function WAREHOUSE:onafterStart(From, Event, To) if self.rail then self.rail:MarkToAll(string.format("%s rail connection.", self.alias), true) end - - -- Create a zone capture object. - self.capturezone=ZONE_CAPTURE_COALITION:New(self.zone, self.coalition) - - -- Add warehouse to zone capture object. Does this work? - self.capturezone.warehouse=self - - -- Start capturing monitoring. - self.capturezone:Start(10, 60) - - -- Handle attack. - function self.capturezone:OnEnterAttacked() - local coalition = self:GetCoalition() - self:E(string.format("Warehouse %s is under attack!", tostring(self.warehouse.alias))) - -- Trigger FSM Attacked event. - self.warehouse:Attacked() - end - - -- Handle capturing. - function self.capturezone:OnEnterCaptured() - local coalition = self:GetCoalition() - self:E(string.format("Warehouse %s was captured by coalition %d!", tostring(self.warehouse.alias), coalition)) - self.warehouse.coalition=coalition --:SetCoalition(coalition) - self.warehouse:Captured(coalition) - self:Guard() - end -- Handle events: self:HandleEvent(EVENTS.Birth, self._OnEventBirth) @@ -616,7 +598,7 @@ function WAREHOUSE:onafterStart(From, Event, To) self:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) -- Start the status monitoring. - self:__Status(5) + self:__Status(1) end --- On after "Stop" event. Stops the warehouse, unhandles all events. @@ -637,8 +619,6 @@ function WAREHOUSE:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.BaseCaptured) - -- Stop capture zone FSM. - self.capturezone:Stop() end --- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. @@ -669,32 +649,11 @@ end function WAREHOUSE:onafterStatus(From, Event, To) self:E(self.wid..string.format("Checking warehouse status of %s", self.alias)) - -- Print queue. - self:_PrintQueue(self.queue, "Queue0:") - self:_PrintQueue(self.pending, "Pending0:") - - -- 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 status. + self:_DisplayStatus() + + -- Check if warehouse is being attacked or has even been captured. + self:_CheckConquered() -- Print queue. self:_PrintQueue(self.queue, "Queue:") @@ -702,14 +661,9 @@ function WAREHOUSE:onafterStatus(From, Event, To) -- Check if requests are valid and remove invalid one. self:_CheckRequestConsistancy(self.queue) - - -- Print queue. - self:_PrintQueue(self.queue, "Queue after consitancy:") - self:_PrintQueue(self.pending, "Pending after consistancy:") - - + -- If warehouse is running than requests can be processed. - if self:IsRunning() then + if self:IsRunning() or self:IsAttacked() then -- Check queue and handle requests if possible. local request=self:_CheckQueue() @@ -719,12 +673,16 @@ function WAREHOUSE:onafterStatus(From, Event, To) end end - -- Print queue. - self:_PrintQueue(self.queue, "Queue after request:") - self:_PrintQueue(self.pending, "Pending after request:") + -- Update warhouse marker on F10 map. + self:_UpdateWarehouseMarkText() + + -- Display complete list of stock itmes. + if self.Debug then + --self:_DisplayStockItems(self.stock) + end - -- Call status again in 30 sec. - self:__Status(30) + -- Call status again in ~30 sec (user choice). + self:__Status(self.dTstatus) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -874,7 +832,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) local distance=self.coordinate:Get2DDistance(Request.warehouse.coordinate) -- Filter the requested assets. - local _assets=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + local _assets,_nasset,_enough=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) -- Check if destination is in range for all requested assets. for _,_asset in pairs(_assets) do @@ -896,8 +854,8 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) end -- Asset is not in stock ==> request denied. - if #_assets < Request.nasset then - local text=string.format("Request denied! Not enough assets currently in stock. Requested %d < %d in stock.", Request.nasset, #_assets) + if not _enough then + local text=string.format("Request denied! Not enough assets currently in stock. Requested %s < %d in stock.", tostring(Request.nasset), _nasset) MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) return false @@ -967,8 +925,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Self request! Assets are only spawned but not routed or transported anywhere. if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then - env.info("FF selfrequest!") - self:__SelfRequest(_spawngroups) + self:E(self.wid..string.format("Selfrequest! Current status %s", self:GetState())) + self:__SelfRequest(1,_spawngroups, Pending) return end @@ -1254,10 +1212,10 @@ end function WAREHOUSE:_SpawnAssetRequest(Request) -- Filter the requested cargo assets. - local _assetstock=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + local _assetstock,_nasset,_enough=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) -- No assets in stock :( - if #_assetstock==0 then + if not _enough then return nil,nil,nil end @@ -1287,7 +1245,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) local _assets={} -- Loop over cargo requests. - for i=1,Request.nasset do + for i=1,#_assetstock do -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Stockitem @@ -1405,7 +1363,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) local ncargo=request.cargogroupset:Count() -- Info - self:E(self.wid..string.format("Cargo %d of %d arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, request.nasset, request.warehouse.alias, ncargo)) + self:E(self.wid..string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo)) -- Move asset into new warehouse. -- TODO: need to figure out which template group name I best take. @@ -1508,8 +1466,9 @@ end -- @param DCS#coalition.side Coalition which is attacking the warehouse. -- @param DCS#country.id Country which is attacking the warehouse. function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) - self:E(self.wid..string.format("Out warehouse is under attack!")) + self:E(self.wid..string.format("Our warehouse is under attack!")) --TODO: Spawn all ground units in the spawnzone? + self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) end --- On after "Defeated" event. Warehouse defeated an attack by another coalition. @@ -1911,6 +1870,7 @@ end --- Checks if the warehouse zone was conquered by antoher coalition. -- @param #WAREHOUSE self function WAREHOUSE:_CheckConquered() + env.info("FF checking conq") local coord=self.zone:GetCoordinate() local radius=self.zone:GetRadius() @@ -1949,6 +1909,9 @@ function WAREHOUSE:_CheckConquered() end end + + -- Debug info. + self:E(self.wid..string.format("Troops at warehouse: blue=%d, red=%d, neutral=%d", Nblue, Nred, Nneutral)) -- Figure out the new coalition if any. @@ -1979,7 +1942,7 @@ function WAREHOUSE:_CheckConquered() if self.coalition==coalition.side.BLUE then -- Blue warehouse is running and we have red units in the zone. if self:IsRunning() and Nred>0 then - self:__Attacked(coalition.side.RED, CountryRed) + self:Attacked(coalition.side.RED, CountryRed) end -- Blue warehouse was under attack by blue but no more blue units in zone. if self:IsAttacked() and Nred==0 then @@ -1988,7 +1951,7 @@ function WAREHOUSE:_CheckConquered() elseif self.coalition==coalition.side.RED then -- Red Warehouse is running and we have blue units in the zone. if self:IsRunning() and Nblue>0 then - self:__Attacked(coalition.side.BLUE, CountryBlue) + self:Attacked(coalition.side.BLUE, CountryBlue) end -- Red warehouse was under attack by blue but no more blue units in zone. if self:IsAttacked() and Nblue==0 then @@ -2248,10 +2211,10 @@ function WAREHOUSE:_CheckRequestNow(request) local okay=true -- Check if number of requested assets is in stock. - local _assets=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) + local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) -- Nothing in stock. - if #_assets==0 then + if _nassets==0 then local text=string.format("Request denied! No assets for this request currently available.") MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) @@ -2263,7 +2226,7 @@ function WAREHOUSE:_CheckRequestNow(request) local _assetcategory=_assets[1].category -- Check if enough assets are in stock. - if request.nasset > #_assets then + if not _enough then local text=string.format("Request denied! Not enough assets currently available.") MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) @@ -2566,40 +2529,55 @@ end -- @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. --- @param #number nmax (Optional) Maximum number of items that will be returned. Default is all matching items are returned. +-- @param #number nmax (Optional) Maximum number of items that will be returned. Default nmax=nil is all matching items are returned. -- @return #table Filtered stock items table. +-- @return #number Total number of (requested) assets available. +-- @return #boolean Enough assets are available. function WAREHOUSE:_FilterStock(stock, item, value, nmax) + -- Default all. + nmax=nmax or "all" + -- Filtered array. local filtered={} + -- Count total number in stock. + local ntot=0 + for _,_stock in ipairs(stock) do + if _stock[item]==value then + ntot=ntot+1 + end + end + + -- Handle string input for nmax. + if type(nmax)=="string" then + if nmax:lower()=="all" then + nmax=ntot + elseif nmax:lower()=="half" then + nmax=ntot/2 + elseif nmax:lower()=="third" then + nmax=ntot/3 + elseif namx:lower()=="quarter" then + nmax=ntot/4 + elseif namx:lower()=="fivth" then + nmax=ntot/5 + else + nmax=math.min(1,ntot) + end + end + -- Loop over stock items. for _i,_stock in ipairs(stock) do if _stock[item]==value then _stock.pos=_i table.insert(filtered, _stock) if nmax~=nil and #filtered>=nmax then - return filtered + return filtered, ntot, true end 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.airbase: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() + return filtered, ntot, nmax>=ntot end --- Check if a group has a generalized attribute. @@ -2768,13 +2746,97 @@ function WAREHOUSE:_PrintQueue(queue, name) self:E(self.wid..name) for _,_qitem in ipairs(queue) do local qitem=_qitem --#WAREHOUSE.Queueitem - local text=self.wid..string.format("UID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%d, Transport=%s, Ntransport=%d", - qitem.uid, qitem.prio, qitem.warehouse.alias, qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval),qitem.nasset,qitem.transporttype,qitem.ntransport) + local text=self.wid..string.format("UID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%s, Transport=%s, Ntransport=%d", + qitem.uid, qitem.prio, qitem.warehouse.alias, qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval), tostring(qitem.nasset),qitem.transporttype,qitem.ntransport) self:E(text) end end +--- Display status of warehouse. +-- @param #WAREHOUSE self +function WAREHOUSE:_DisplayStatus() + -- Set airbase name. + local airbasename="none" + if self.airbase then + airbasename=self.airbase:GetName() + end + + local text=string.format("\n------------------------------------------------------\n") + text=text..string.format("Warehouse %s status:\n", self.alias) + text=text..string.format("------------------------------------------------------\n") + text=text..string.format("Current status = %s\n", self:GetState()) + 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) + text=text..string.format("Queued requests = %d\n", #self.queue) + text=text..string.format("Pending requests = %d\n", #self.pending) + text=text..string.format("------------------------------------------------------\n") + text=text..self:_GetStockAssetsText() + env.info(text) + --TODO: number of ground, air, naval assets. +end + +--- Get text about warehouse stock. +-- @param #WAREHOUSE self +-- @param #boolean messagetoall If true, send message to all. +-- @return #string Text about warehouse stock +function WAREHOUSE:_GetStockAssetsText(messagetoall) + + -- Get assets in stock. + local _data=self:GetStockInfo(self.stock) + + -- Text. + local text="Stock:\n" + for _attribute,_count in pairs(_data) do + text=text..string.format("%s = %d\n", _attribute,_count) + end + text=text..string.format("------------------------------------------------------\n") + + -- Send message? + MESSAGE:New(text, 10):ToAllIf(messagetoall) + + return text +end + +--- Create or update mark text at warehouse, which is displayed in F10 map. +-- Only the coaliton of the warehouse owner is able to see it. +-- @param #WAREHOUSE self +-- @return #string Text about warehouse stock +function WAREHOUSE:_UpdateWarehouseMarkText() + + -- Create a mark with the current assets in stock. + if self.markerid~=nil then + trigger.action.removeMark(self.markerid) + end + + -- Get assets in stock. + local _data=self:GetStockInfo(self.stock) + + -- Create mark text. + local marktext="Warehouse stock:\n" + for _attribute,_count in pairs(_data) do + marktext=marktext..string.format("%s=%d, ", _attribute,_count) -- Dont use \n because too many make DCS crash! + end + + -- Create/update marker at warehouse in F10 map. + self.markerid=self.coordinate:MarkToCoalition(marktext, self.coalition, true) +end + +--- Display stock items of warehouse. +-- @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.airbase: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 --- Make a flight plan from a departure to a destination airport. -- @param #WAREHOUSE self From 8bcdbef4262a997dfe31d2871db6485d2f1ea042 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 19 Aug 2018 00:19:55 +0200 Subject: [PATCH 17/73] Warehouse v0.2.1 --- Moose Development/Moose/Core/Point.lua | 42 +- Moose Development/Moose/Core/Spawn.lua | 3 +- .../Moose/Functional/Warehouse.lua | 524 +++++++++++++----- Moose Development/Moose/Wrapper/Airbase.lua | 9 +- 4 files changed, 440 insertions(+), 138 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 77aedec5c..b67ed1bef 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1275,9 +1275,12 @@ do -- COORDINATE -- @param #COORDINATE ToCoord Coordinate of destination. -- @param #boolean IncludeEndpoints (Optional) Include the coordinate itself and the ToCoordinate in the path. -- @param #boolean Railroad (Optional) If true, path on railroad is returned. Default false. + -- @param #boolean MarkPath (Optional) If true, place markers on F10 map along the path. + -- @param #boolean SmokePath (Optional) If true, put (green) smoke along the -- @return #table Table of coordinates on road. If no path on road can be found, nil is returned or just the endpoints. - -- @return #number The length of the total path. - function COORDINATE:GetPathOnRoad(ToCoord, IncludeEndpoints, Railroad) + -- @return #number Tonal length of path. + -- @return #boolean If true a valid path on road/rail was found. If false, only the direct way is possible. + function COORDINATE:GetPathOnRoad(ToCoord, IncludeEndpoints, Railroad, MarkPath, SmokePath) -- Set road type. local RoadType="roads" @@ -1296,20 +1299,43 @@ do -- COORDINATE if IncludeEndpoints then Path[1]=self end + + -- Assume we could get a valid path. + local GotPath=true -- Check that DCS routine actually returned a path. There are situations where this is not the case. if path then -- Include all points on road. - for _,_vec2 in ipairs(path) do - Path[#Path+1]=COORDINATE:NewFromVec2(_vec2) - --COORDINATE:NewFromVec2(_vec2):SmokeGreen() + for _i,_vec2 in ipairs(path) do + + local coord=COORDINATE:NewFromVec2(_vec2) + + Path[#Path+1]=coord + + if MarkPath then + coord:MarkToAll(string.format("Path segment %d.", _i)) + end + if SmokePath then + coord:SmokeGreen() + end + end + + -- Mark/smoke endpoints + if IncludeEndpoints then + if MarkPath then + COORDINATE:NewFromVec2(path[1]):MarkToAll("Path Initinal Point") + COORDINATE:NewFromVec2(path[1]):MarkToAll("Path Final Point") + end + if SmokePath then + COORDINATE:NewFromVec2(path[1]):SmokeBlue() + COORDINATE:NewFromVec2(path[#path]):SmokeBlue() + end end - --COORDINATE:NewFromVec2(path[1]):SmokeBlue() - --COORDINATE:NewFromVec2(path[#path]):SmokeBlue() else self:E("Path is nil. No valid path on road could be found.") + GotPath=false end -- Include end point, which might not be on road. @@ -1327,7 +1353,7 @@ do -- COORDINATE return nil,nil end - return Path, Way + return Path, Way, GotPath end --- Gets the surface type at the coordinate. diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index bd2f4653c..563009ed1 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1613,13 +1613,14 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + self:E(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y + parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end else diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index ac3aa3cff..70375574b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -41,7 +41,7 @@ -- @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}. --- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table entries are of type @{#WAREHOUSE.Queueitem}. +-- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table entries are of type @{#WAREHOUSE.Pendingitem}. -- @extends Core.Fsm#FSM --- Manages ground assets of an airbase and offers the possibility to transport them to another airbase or warehouse. @@ -196,7 +196,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.0" +WAREHOUSE.version="0.2.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -431,7 +431,7 @@ end -- @param #WAREHOUSE self -- @param #number timeinterval Time interval in seconds. -- @return #WAREHOUSE self -function WAREHOUSE:SetSpawnZone(timeinterval) +function WAREHOUSE:SetStatusUpdate(timeinterval) self.dTstatus=timeinterval return self end @@ -516,6 +516,25 @@ function WAREHOUSE:IsAttacked() return self:is("Attacked") end +--- Check if the warehouse has a road connection to another warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. +-- @param #boolean markpath If true, place markers of path segments on the F10 map. +-- @param #boolean smokepath If true, put green smoke on path segments. +-- @return #boolean If true, the two warehouses are connected by road. +-- @return #number Path length in meters. +function WAREHOUSE:HasConnectionRoad(warehouse, markpath, smokepath) + if warehouse then + if self.road and warehouse.road then + local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road, false, false, markpath, smokepath) + return gotpath, length or 0 + else + -- At least one of the warehouses has no road connection. + return false, 0 + end + end + return nil +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states @@ -714,6 +733,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) local DCStype=DCSunit:getTypeName() local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() + group:GetCategory() env.info(string.format("New asset for warehouse %s:", self.alias)) env.info(string.format("Group name = %s", group:GetName())) @@ -764,6 +784,221 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) end +--- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. +-- @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. +function WAREHOUSE:_AddAssetFromZombie(group, ngroups) + +end + + +--- Spawn a ground asset in the spawnzone of the warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. +function WAREHOUSE:_SpawnGroundAsset(asset, request) + + if asset and asset.category==Group.Category.GROUND then + + -- Prepare spawn template. + local template=self:_SpawnAssetPrepareTemplate(asset, request) + + -- Initial spawn point. + template.route.points[1] = {} + + -- Get a random coordinate in the spawn zone. + local coord=self.spawnzone:GetRandomCoordinate() + + -- Translate the position of the units. + for i=1,#template.units do + + -- Unit template. + local unit = template.units[i] + + -- Translate position. + local SX = unit.x or 0 + local SY = unit.y or 0 + local BX = asset.template.route.points[1].x + local BY = asset.template.route.points[1].y + local TX = coord.x + (SX-BX) + local TY = coord.z + (SY-BY) + + template.units[i].x = TX + template.units[i].y = TY + + end + + template.route.points[1].x = coord.x + template.route.points[1].y = coord.z + + template.x = coord.x + template.y = coord.z + template.alt = coord.y + + -- Spawn group. + local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP + + -- Activate group. + group:Activate() + + return group + end + + return nil +end + +--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. +function WAREHOUSE:_SpawnAssetAircraft(asset, request) + + if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then + + -- Prepare spawn template. + local template=self:_SpawnAssetPrepareTemplate(asset, request) + + -- Set and empty route. + if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then + --template.route.points=self:_GetFlightplan(group,_departure,_destination) + template.route.points[1] = {} + else + template.route.points[1] = {} + end + + -- Link. + local spawnpoint=template.route.points[1] + + -- Set initial waypoint type/action ==> cold start. + spawnpoint.type = COORDINATE.WaypointType.TakeOffParking + spawnpoint.action = COORDINATE.WaypointAction.FromParkingArea + + -- Get airbase ID and category. + local AirbaseID = self.airbase:GetID() + local AirbaseCategory = self.category + + -- Set airbase ID. + if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then + spawnpoint.helipadId = AirbaseID + spawnpoint.linkUnit = AirbaseID + spawnpoint.airdromeId = nil + elseif AirbaseCategory == Airbase.Category.AIRDROME then + spawnpoint.airdromeId = AirbaseID + spawnpoint.linkUnit = nil + spawnpoint.helipadId = nil + end + + -- Get the right terminal type for this kind of aircraft. + local terminaltype=self:_GetTerminal(asset.attribute) + + -- Get parking data. + local parking=self.airbase:GetFreeParkingSpotsTable(terminaltype) + + if #parking<#template.units then + self:E("ERROR: Not enough parking!") + return nil + end + + -- Position the units. + for i=1,#template.units do + + -- Unit template. + local unit = template.units[i] + + -- Nillify the unit ID. + unit.unitId=nil + + -- Set unit name. + unit.name=string.format("%s-%02d", template.name , i) + + local coord=parking[i].Coordinate --Core.Point#COORDINATE + local terminal=parking[i].TerminalID --#number + + coord:MarkToAll(string.format("spawnplace terminal %d", terminal)) + + unit.x=coord.x + unit.y=coord.z + unit.alt=coord.y + + unit.parking_id = nil + unit.parking = terminal + end + + -- Set general spawnpoint position. + local abc=self.airbase:GetCoordinate() + spawnpoint.x = template.units[1].x + spawnpoint.y = template.units[1].y + spawnpoint.alt = template.units[1].alt + + -- And template position. + template.x = template.units[1].x + template.y = template.units[1].y + + -- Uncontrolled spawning. + template.uncontrolled=true + self:E({airtemplate=template}) + + + -- Spawn group. + local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP + + -- Activate group. + group:Activate() + + return group + end + + return nil +end + + +--- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @return #template Prepared new spawn template. +function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, request) + + -- Create an own copy of the template! + local template=UTILS.DeepCopy(asset.template) + + -- Set unique name. + template.name=self:_Alias(asset, request) + + -- Set current(!) coalition and country. + template.CoalitionID=self.coalition + template.CountryID=self.country + + -- Nillify the group ID. + template.groupId=nil + + -- No late activation. + template.lateActivation=false + + -- Set and empty route. + template.route = {} + template.route.points = {} + + -- Handle units. + for i=1,#template.units do + + -- Unit template. + local unit = template.units[i] + + -- Nillify the unit ID. + unit.unitId=nil + + -- Set unit name. + unit.name=string.format("%s-%02d", template.name , i) + + end + + return template +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "AddRequest" event. Add a request to the warehouse queue, which is processed when possible. @@ -877,61 +1112,50 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Cargo assets. ------------------------------------------------------------------------------------------------------------------------------------ - -- Pending request. - local Pending=Request --#WAREHOUSE.Pendingitem - - -- Spawn assets. + -- Spawn assets of this request. local _spawngroups,_cargoassets=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP + -- Check if any group was spawned. If not, delete the request. + if _spawngroups:Count()==0 then + -- Delete request from queue. + self:_DeleteQueueItem(Request, self.queue) + self:E(self.wid..string.format("ERROR: Groups or request %d could not be spawned. Request is rejected and deleted from queue!", Request.uid)) + return + end + -- General type and category. local _cargotype=_cargoassets[1].attribute --#WAREHOUSE.Attribute local _cargocategory=_cargoassets[1].category --DCS#Group.Category - - -- Add cargo groups to request. + + -- Pending request. Add cargo groups to request. + local Pending=Request --#WAREHOUSE.Pendingitem Pending.cargogroupset=_spawngroups Pending.cargoassets=_cargoassets Pending.cargoattribute=_cargotype Pending.cargocategory=_cargocategory - - -- Add groups to cargo if they don't go by themselfs. - local CargoGroups --Core.Set#SET_CARGO - if Request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then - - --TODO: make nearradius depended on transport type and asset type. - local _loadradius=5000 - local _nearradius=35 - - if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - _loadradius=5000 - elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - _loadradius=500 - elseif Request.transporttype==WAREHOUSE.TransportType.APC then - _loadradius=100 - end - - -- Empty cargo group set. - CargoGroups = SET_CARGO:New() - - -- Add cargo groups to set. - for _i,_group in pairs(_spawngroups:GetSetObjects()) do - local group=_group --Wrapper.Group#GROUP - local _wid,_aid,_rid=self:_GetIDsFromGroup(group) - local _alias=self:_alias(group:GetTypeName(),_wid,_aid,_rid) - local cargogroup = CARGO_GROUP:New(_group, _cargotype,_alias,_loadradius,_nearradius) - CargoGroups:AddCargo(cargogroup) - end - - end + ------------------------------------------------------------------------------------------------------------------------------------ + -- Self request: assets are spawned at warehouse but not transported anywhere. + ------------------------------------------------------------------------------------------------------------------------------------ + -- Self request! Assets are only spawned but not routed or transported anywhere. if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then self:E(self.wid..string.format("Selfrequest! Current status %s", self:GetState())) + + -- Add request to pending queue. + table.insert(self.pending, Pending) + + -- Delete request from queue. + self:_DeleteQueueItem(Request, self.queue) + + -- Start self request. self:__SelfRequest(1,_spawngroups, Pending) + return - end - + end + ------------------------------------------------------------------------------------------------------------------------------------ - -- Self propelled assets. + -- Self propelled: assets go to the requesting warehouse by themselfs. ------------------------------------------------------------------------------------------------------------------------------------ -- No transport unit requested. Assets go by themselfes. @@ -977,9 +1201,40 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- No cargo transport necessary. return end + + ------------------------------------------------------------------------------------------------------------------------------------ + -- Prepare cargo groups for transport + ------------------------------------------------------------------------------------------------------------------------------------ + + -- Add groups to cargo if they don't go by themselfs. + local CargoGroups --Core.Set#SET_CARGO + + --TODO: make nearradius depended on transport type and asset type. + local _loadradius=5000 + local _nearradius=35 + + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + _loadradius=5000 + elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + _loadradius=500 + elseif Request.transporttype==WAREHOUSE.TransportType.APC then + _loadradius=100 + end + + -- Empty cargo group set. + CargoGroups = SET_CARGO:New() + + -- Add cargo groups to set. + for _i,_group in pairs(_spawngroups:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + local _wid,_aid,_rid=self:_GetIDsFromGroup(group) + local _alias=self:_alias(group:GetTypeName(),_wid,_aid,_rid) + local cargogroup = CARGO_GROUP:New(_group, _cargotype,_alias,_loadradius,_nearradius) + CargoGroups:AddCargo(cargogroup) + end ------------------------------------------------------------------------------------------------------------------------------------ - -- Transport assets and dispachers + -- Transport assets and dispatchers ------------------------------------------------------------------------------------------------------------------------------------ -- Set of cargo carriers. @@ -1204,7 +1459,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end ---- Spawns requested asset at warehouse or associated airbase. +--- Spawns requested assets at warehouse or associated airbase. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Queueitem Request Information table of the request. -- @return Core.Set#SET_GROUP Set of groups that were spawned. @@ -1258,7 +1513,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Spawn object. Spawn with ALIAS here or DCS crashes! --local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country):InitUnControlled(UnControlled):InitAIOnOff(AIOnOff) + --local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country):InitUnControlled(UnControlled):InitAIOnOff(AIOnOff) local _group=nil --Wrapper.Group#GROUP local _attribute=_assetitem.attribute @@ -1266,14 +1521,16 @@ function WAREHOUSE:_SpawnAssetRequest(Request) if _assetitem.category==Group.Category.GROUND then -- Spawn ground troops. - _group=_spawn:SpawnFromCoordinate(spawncoord) + --_group=_spawn:SpawnFromCoordinate(spawncoord) + _group=self:_SpawnGroundAsset(_assetitem, Request) elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! -- Spawn air units. - _group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) + --_group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) + _group=self:_SpawnAssetAircraft(_assetitem, Request) elseif _assetitem.category==Group.Category.TRAIN then @@ -1286,7 +1543,6 @@ function WAREHOUSE:_SpawnAssetRequest(Request) end if _group then - --_spawngroups[i]=_group _groupset:AddGroup(_group) table.insert(_assets, _assetitem) else @@ -1365,9 +1621,14 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) -- Info self:E(self.wid..string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo)) + -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. + if group:IsGround() and group:GetSpeedMax()>1 then + group:RouteGroundTo(request.warehouse.coordinate, group:GetSpeedMax()*0.3, "Off Road") + end + -- Move asset into new warehouse. -- TODO: need to figure out which template group name I best take. - request.warehouse:__AddAsset(3, group:GetName(), 1) + request.warehouse:__AddAsset(60, group:GetName(), 1) -- All cargo delivered. if request and ncargo==0 then @@ -1447,10 +1708,13 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) self:E(self.wid..string.format("Assets spawned at warehouse %s after self request!", self.alias)) + env.info("FF spawned groupas at self request") -- Put assets in new warehouse. for _,_group in pairs(groupset:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP - group:SmokeGreen() + local text=string.format("Group name = %s, IsAlive=%s.", tostring(group:GetName()), tostring(group:IsAlive())) + env.info(text) + --group:SmokeGreen() end -- Remove pending request: @@ -1870,8 +2134,8 @@ end --- Checks if the warehouse zone was conquered by antoher coalition. -- @param #WAREHOUSE self function WAREHOUSE:_CheckConquered() - env.info("FF checking conq") + -- Get coordinate and radius to check. local coord=self.zone:GetCoordinate() local radius=self.zone:GetRadius() @@ -1892,26 +2156,30 @@ function WAREHOUSE:_CheckConquered() for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT - -- Get coalition and country. - local _coalition=unit:GetCoalition() - local _country=unit:GetCountry() - - if _coalition==coalition.side.BLUE then - Nblue=Nblue+1 - CountryBlue=_country - elseif _coalition==coalition.side.RED then - Nred=Nred+1 - CountryRed=_country - else - Nneutral=Nneutral+1 - CountryNeutral=_country - end + -- Filter only groud units. + if unit:IsGround() then + -- Get coalition and country. + local _coalition=unit:GetCoalition() + local _country=unit:GetCountry() + + if _coalition==coalition.side.BLUE then + Nblue=Nblue+1 + CountryBlue=_country + elseif _coalition==coalition.side.RED then + Nred=Nred+1 + CountryRed=_country + else + Nneutral=Nneutral+1 + CountryNeutral=_country + end + + end end end -- Debug info. - self:E(self.wid..string.format("Troops at warehouse: blue=%d, red=%d, neutral=%d", Nblue, Nred, Nneutral)) + self:E(self.wid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d", Nblue, Nred, Nneutral)) -- Figure out the new coalition if any. @@ -1929,12 +2197,13 @@ function WAREHOUSE:_CheckConquered() elseif Nblue==0 and Nred==0 and Nneutral>0 then -- Only neutral units in zone but neutrals do not attack or even capture! --newcoalition=coalition.side.NEUTRAL - newcountry=CountryNeutral + --newcountry=CountryNeutral end - -- Coalition has changed ==> warehouse was captured! + -- Coalition has changed ==> warehouse was captured! This should be before the attack check. if self:IsAttacked() and newcoalition ~= self.coalition then - self:Captured(newcoalition, newcountry) + self:Captured(newcoalition, newcountry) + return end -- Before a warehouse can be captured, it has to be attacked. @@ -2202,7 +2471,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) end --- Checks if the request can be fullfilled right now. --- Check for current parking situation, number of assets and transports currently in stock +-- Check for current parking situation, number of assets and transports currently in stock. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Queueitem request The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. @@ -2213,70 +2482,70 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check if number of requested assets is in stock. local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) - -- Nothing in stock. - if _nassets==0 then - local text=string.format("Request denied! No assets for this request currently available.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - return false - end - - -- Get the attibute of the requested asset. - local _assetattribute=_assets[1].attribute - local _assetcategory=_assets[1].category - -- Check if enough assets are in stock. if not _enough then - local text=string.format("Request denied! Not enough assets currently available.") + local text=string.format("Request denied! Not enough (cargo) assets currently available.") MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) okay=false end - -- Check available parking for asset units. - local Parkingdata - local Parking - if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then - Parkingdata=self.airbase:GetParkingSpotsTable() - Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) - if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end - end - - - -- Check that a transport units. - if request.transporttype~=WAREHOUSE.TransportType.SELFPROPELLED then - - -- Transports in stock. - local _transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) - - -- Get the attibute of the transport units. - local _transportattribute=_transports[1].attribute - local _transportcategory=_transports[1].category - - -- Check if enough transport units are available. - if request.ntransport > #_transports then - local text=string.format("Request denied! Not enough transport units currently available.") - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - okay=false - end + -- Check if at least one transport asset is available. + if _nassets>0 then - -- Check available parking for transport units. - if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then - Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) + -- Get the attibute of the requested asset. + local _assetattribute=_assets[1].attribute + local _assetcategory=_assets[1].category + + -- Check available parking for asset units. + local Parkingdata + local Parking + if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then + Parkingdata=self.airbase:GetParkingSpotsTable() + Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") + local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) okay=false end end - + + end + + -- Check that a transport units. + if request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then + + -- Transports in stock. + local _transports,_ntransports,_enough=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) + + -- Check if enough transport units are available. + if not _enough then + local text=string.format("Request denied! Not enough transport units currently available.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + + -- Check if at least one transport asset is available. + if _ntransports>0 then + + -- Get the attibute of the transport units. + local _transportattribute=_transports[1].attribute + local _transportcategory=_transports[1].category + + -- Check available parking for transport units. + if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then + Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) + if Parking==nil then + local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + end + + end else -- self propelled case. @@ -2397,7 +2666,9 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) for _,spot in pairs(spots) do if spot then local coord=spot.Coordinate --Core.Point#COORDINATE - coord:MarkToAll("Parking spot for "..asset.templatename.." id "..asset.uid.." terminal id "..spot.TerminalID) + local text=string.format("Parking spot for %s:\nAsset id=%d, Terminal id=%d", asset.templatename, asset.uid, spot.TerminalID) + coord:MarkToAll(text) + self:E(self.wid..text) end end @@ -2532,7 +2803,7 @@ end -- @param #number nmax (Optional) Maximum number of items that will be returned. Default nmax=nil is all matching items are returned. -- @return #table Filtered stock items table. -- @return #number Total number of (requested) assets available. --- @return #boolean Enough assets are available. +-- @return #boolean If true, enough assets are available. function WAREHOUSE:_FilterStock(stock, item, value, nmax) -- Default all. @@ -2557,9 +2828,9 @@ function WAREHOUSE:_FilterStock(stock, item, value, nmax) nmax=ntot/2 elseif nmax:lower()=="third" then nmax=ntot/3 - elseif namx:lower()=="quarter" then + elseif nmax:lower()=="quarter" then nmax=ntot/4 - elseif namx:lower()=="fivth" then + elseif nmax:lower()=="fivth" then nmax=ntot/5 else nmax=math.min(1,ntot) @@ -2577,7 +2848,7 @@ function WAREHOUSE:_FilterStock(stock, item, value, nmax) end end - return filtered, ntot, nmax>=ntot + return filtered, ntot, ntot>=nmax end --- Check if a group has a generalized attribute. @@ -2718,9 +2989,12 @@ end -- @param #WAREHOUSE.Queueitem qitem Item of queue to be removed. -- @param #table queue The queue from which the item should be deleted. function WAREHOUSE:_DeleteQueueItem(qitem, queue) + self:F({qitem=qitem, queue=queue}) + for i=1,#queue do local _item=queue[i] --#WAREHOUSE.Queueitem if _item.uid==qitem.uid then + self:E(self.wid..string.format("Deleting queue item %d.", qitem.uid)) table.remove(queue,i) break end diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 7d45a38b8..7c9da217c 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -698,11 +698,12 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, -- Check all units. for _,unit in pairs(_units) do - - local _vec3=unit:getPoint() - local _coord=COORDINATE:NewFromVec3(_vec3) + -- Unis are now returned as MOOSE units not DCS units! + --local _vec3=unit:getPoint() + --local _coord=COORDINATE:NewFromVec3(_vec3) + local _coord=unit:GetCoordinate() local _dist=_coord:Get2DDistance(_spot) - local _safe=_overlap(aircraft, true, unit, false,_dist) + local _safe=_overlap(aircraft, true, unit, true,_dist) if markobstacles then local l,x,y,z=_GetObjectSize(unit) From d1aa5d5de3768501e170ab23dab16749695c3d3a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 20 Aug 2018 00:08:19 +0200 Subject: [PATCH 18/73] Warehouse v0.2.2 --- Moose Development/Moose/Core/Database.lua | 4 +- Moose Development/Moose/Core/Point.lua | 1 + .../Moose/Functional/Warehouse.lua | 400 ++++++++++-------- Moose Development/Moose/Wrapper/Group.lua | 26 +- 4 files changed, 229 insertions(+), 202 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 3b0fb0337..85e78482a 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -533,8 +533,8 @@ end -- SpawnCountryID, SpawnCategoryID -- This method is used by the SPAWN class. -- @param #DATABASE self --- @param #table SpawnTemplate --- @return #DATABASE self +-- @param #table SpawnTemplate Template of the group to spawn. +-- @return Wrapper.Group#GROUP Spawned group. function DATABASE:Spawn( SpawnTemplate ) self:F( SpawnTemplate.name ) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index b67ed1bef..c899a48c6 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -1028,6 +1028,7 @@ do -- COORDINATE RoutePoint.task.params = {} RoutePoint.task.params.tasks = DCSTasks or {} + self:E({RoutePoint=RoutePoint}) return RoutePoint end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 70375574b..a602549a3 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -140,12 +140,11 @@ WAREHOUSE = { --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem -- @extends #WAREHOUSE.Queueitem --- @field #table cargoassets Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}. +-- @field #table assets Table of assets - cargo or transport. Each element of the table is a @{#WAREHOUSE.Stockitem}. -- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. -- @field #number ndelivered Number of groups delivered to destination. -- @field #number cargoattribute Attribute of cargo assets of type @{#WAREHOUSE.Attribute}. -- @field #number cargocategory Category of cargo assets of type @{#WAREHOUSE.Category}. --- @field #table transportassets Table of assets to be delivered. Each element of the table is a @{#WAREHOUSE.Stockitem}. -- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. -- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}. -- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. @@ -196,7 +195,7 @@ WAREHOUSE.TransportType = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.1" +WAREHOUSE.version="0.2.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -516,24 +515,24 @@ function WAREHOUSE:IsAttacked() return self:is("Attacked") end ---- Check if the warehouse has a road connection to another warehouse. +--- Check if the warehouse has a road connection to another warehouse. Both warehouses need to be running! -- @param #WAREHOUSE self -- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. -- @param #boolean markpath If true, place markers of path segments on the F10 map. -- @param #boolean smokepath If true, put green smoke on path segments. -- @return #boolean If true, the two warehouses are connected by road. --- @return #number Path length in meters. +-- @return #number Path length in meters. Negative distance -1 meter indicates no connection. function WAREHOUSE:HasConnectionRoad(warehouse, markpath, smokepath) if warehouse then if self.road and warehouse.road then local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road, false, false, markpath, smokepath) - return gotpath, length or 0 + return gotpath, length or -1 else -- At least one of the warehouses has no road connection. - return false, 0 + return false, -1 end end - return nil + return nil, -1 end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -711,66 +710,36 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #string templategroupname Name of the late activated template group as defined in the mission editor. +-- @param Wrapper.Group#GROUP group Group or template group to be added to the warehouse stock. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. -function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) +function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) -- Set default. local n=ngroups or 1 + + local asset --Wrapper.Group#GROUP + if type(group)=="string" then + env.info("New asset passed as string") + asset=GROUP:FindByName(group) + elseif type(group)=="table" and group.aid~=nil then + env.info("New asset passed as asset") + self:_AddExistingAsset(group) + return + elseif group:IsInstanceOf("GROUP") then + env.info("New asset passed as string") + asset=group + else + self:E("ERROR: Invalid asset added!") + return nil + end - -- Get MOOSE group. - local group=GROUP:FindByName(templategroupname) -- Check if group exists and has a DCS object. -- TODO: Need to check this carefully if this words with CARGO etc. - if group and group:IsAlive()~=nil and group:GetDCSObject() then + if asset and asset:IsAlive()~=nil and asset:GetDCSObject() then - local DCSgroup=group:GetDCSObject() - local DCSunit=DCSgroup:getUnit(1) - local DCSdesc=DCSunit:getDesc() - local DCSdisplay=DCSdesc.displayName - local DCScategory=DCSgroup:getCategory() - local DCStype=DCSunit:getTypeName() - local SpeedMax=group:GetSpeedMax() - local RangeMin=group:GetRange() - group:GetCategory() - - env.info(string.format("New asset for warehouse %s:", self.alias)) - 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)) - env.info(string.format("Speed max = %s km/h", tostring(SpeedMax))) - env.info(string.format("Range min = %s m", tostring(RangeMin))) - self:E({fullassetdesc=DCSdesc}) - - -- Get the generalized attribute. - local attribute=self:_GetAttribute(templategroupname) - - -- Add this n times to the table. - for i=1,n do - local stockitem={} --#WAREHOUSE.Stockitem - - -- Increase asset unique id counter. - self.assetid=self.assetid+1 - - -- Set parameters. - stockitem.uid=self.assetid - stockitem.templatename=templategroupname - stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) - stockitem.category=DCScategory - stockitem.unittype=DCStype - stockitem.attribute=attribute - stockitem.range=RangeMin - stockitem.speedmax=SpeedMax - - -- Modify the template so that the group is spawned with the right coalition. - -- TODO: somehow this is now acknoleged properly. Found a workaround however with SPAWN AIP functions. - stockitem.template.CoalitionID=self.coalition - stockitem.template.CountryID=self.country - - table.insert(self.stock, stockitem) - end + -- Add new asset. + self:_AddNewAsset(asset, ngroups) -- Destroy group if it is alive. if group:IsAlive()==true then @@ -784,6 +753,73 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, templategroupname, ngroups) end +--- Add an existing asset to the warehouse stock. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Stockitem asset The asset to be added. +function WAREHOUSE:_AddExistingAsset(asset) + table.insert(self.stock, asset) +end + +--- Add a completely new asset to the warehouse. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group that will be added to the warehouse stock. +-- @param #number ngroups Number of groups to be added. +function WAREHOUSE:_AddNewAsset(group, ngroups) + + -- Set default. + local n=ngroups or 1 + + local templategroupname=group:GetName() + + local DCSgroup=group:GetDCSObject() + local DCSunit=DCSgroup:getUnit(1) + local DCSdesc=DCSunit:getDesc() + local DCSdisplay=DCSdesc.displayName + local DCScategory=DCSgroup:getCategory() + local DCStype=DCSunit:getTypeName() + local SpeedMax=group:GetSpeedMax() + local RangeMin=group:GetRange() + group:GetCategory() + + env.info(string.format("New asset for warehouse %s:", self.alias)) + 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)) + env.info(string.format("Speed max = %s km/h", tostring(SpeedMax))) + env.info(string.format("Range min = %s m", tostring(RangeMin))) + self:E({fullassetdesc=DCSdesc}) + + -- Get the generalized attribute. + local attribute=self:_GetAttribute(templategroupname) + + -- Add this n times to the table. + for i=1,n do + local stockitem={} --#WAREHOUSE.Stockitem + + -- Increase asset unique id counter. + self.assetid=self.assetid+1 + + -- Set parameters. + stockitem.uid=self.assetid + stockitem.templatename=templategroupname + stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) + stockitem.category=DCScategory + stockitem.unittype=DCStype + stockitem.attribute=attribute + stockitem.range=RangeMin + stockitem.speedmax=SpeedMax + + -- Modify the template so that the group is spawned with the right coalition. + -- TODO: somehow this is now acknoleged properly. Found a workaround however with SPAWN AIP functions. + stockitem.template.CoalitionID=self.coalition + stockitem.template.CountryID=self.country + + table.insert(self.stock, stockitem) + end + +end + --- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. -- @param #WAREHOUSE self -- @param #string templategroupname Name of the late activated template group as defined in the mission editor. @@ -798,7 +834,7 @@ end -- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnGroundAsset(asset, request) +function WAREHOUSE:_SpawnAssetGround(asset, request) if asset and asset.category==Group.Category.GROUND then @@ -853,8 +889,9 @@ end -- @param #WAREHOUSE self -- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @param #table parking Parking data for this asset. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(asset, request) +function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then @@ -892,39 +929,56 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request) end -- Get the right terminal type for this kind of aircraft. - local terminaltype=self:_GetTerminal(asset.attribute) + --local terminaltype=self:_GetTerminal(asset.attribute) -- Get parking data. - local parking=self.airbase:GetFreeParkingSpotsTable(terminaltype) + --local parking=self.airbase:GetFreeParkingSpotsTable(terminaltype) - if #parking<#template.units then - self:E("ERROR: Not enough parking!") - return nil + if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then + --TODO Figure out what's necessary in this case. + + else + + if #parking<#template.units then + local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) + self:E(text) + return nil + end end - + -- Position the units. for i=1,#template.units do -- Unit template. local unit = template.units[i] - - -- Nillify the unit ID. - unit.unitId=nil - - -- Set unit name. - unit.name=string.format("%s-%02d", template.name , i) - - local coord=parking[i].Coordinate --Core.Point#COORDINATE - local terminal=parking[i].TerminalID --#number - - coord:MarkToAll(string.format("spawnplace terminal %d", terminal)) - - unit.x=coord.x - unit.y=coord.z - unit.alt=coord.y - unit.parking_id = nil - unit.parking = terminal + if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then + + -- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense. + local coord=self.airbase:GetCoordinate() + + unit.x=coord.x + unit.y=coord.z + unit.alt=coord.y + + unit.parking_id = nil + unit.parking = nil + + else + + local coord=parking[i].Coordinate --Core.Point#COORDINATE + local terminal=parking[i].TerminalID --#number + + coord:MarkToAll(string.format("spawnplace unit %s terminal %d", unit.name, terminal)) + + unit.x=coord.x + unit.y=coord.z + unit.alt=coord.y + + unit.parking_id = nil + unit.parking = terminal + + end end -- Set general spawnpoint position. @@ -939,6 +993,8 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request) -- Uncontrolled spawning. template.uncontrolled=true + + -- Debug info. self:E({airtemplate=template}) @@ -1111,9 +1167,13 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) ------------------------------------------------------------------------------------------------------------------------------------ -- Cargo assets. ------------------------------------------------------------------------------------------------------------------------------------ + + -- Pending request. Add cargo groups to request. + local Pending=Request --#WAREHOUSE.Pendingitem + Pending.assets={} -- Spawn assets of this request. - local _spawngroups,_cargoassets=self:_SpawnAssetRequest(Request) --Core.Set#SET_GROUP + local _spawngroups,_cargoassets=self:_SpawnAssetRequest(Pending) --Core.Set#SET_GROUP -- Check if any group was spawned. If not, delete the request. if _spawngroups:Count()==0 then @@ -1123,14 +1183,11 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) return end - -- General type and category. + -- General type and category. local _cargotype=_cargoassets[1].attribute --#WAREHOUSE.Attribute local _cargocategory=_cargoassets[1].category --DCS#Group.Category - -- Pending request. Add cargo groups to request. - local Pending=Request --#WAREHOUSE.Pendingitem Pending.cargogroupset=_spawngroups - Pending.cargoassets=_cargoassets Pending.cargoattribute=_cargotype Pending.cargocategory=_cargocategory @@ -1175,9 +1232,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:_RouteGround(group, ToCoordinate) elseif _cargocategory==Group.Category.AIRPLANE then env.info("FF route plane "..group:GetName()) - --self:_RouteAir(group, Request.airbase) - -- TEST! - group=self:_RouteAirRat(group, Request.airbase) + self:_RouteAir(group, Request.airbase) elseif _cargocategory==Group.Category.HELICOPTER then env.info("FF route helo "..group:GetName()) self:_RouteAir(group, Request.airbase) @@ -1291,6 +1346,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) + Pending.assets[_assetitem.uid]=_assetitem table.insert(_transportassets,_assetitem) end end @@ -1329,6 +1385,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) + Pending.assets[_assetitem.uid]=_assetitem table.insert(_transportassets,_assetitem) else self:E(self.wid.."ERROR: spawngroup helo transport does not exist!") @@ -1370,6 +1427,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add group to transportset. TransportSet:AddGroup(spawngroup) + Pending.assets[_assetitem.uid]=_assetitem table.insert(_transportassets,_assetitem) end end @@ -1440,13 +1498,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Start dispatcher. CargoTransport:__Start(5) - - -- Add transportassets to pending queue item. - Pending.transportassets=_transportassets - + -- Add cargo groups to request. Pending.transportgroupset=TransportSet - Pending.transportassets=_transportassets Pending.transportattribute=_transporttype Pending.transportcategory=_transportcategory @@ -1481,7 +1535,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_GetParkingForAssets(_assetstock) + Parking=self:_GetParkingForAssets(_assetstock) end -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. @@ -1492,11 +1546,10 @@ function WAREHOUSE:_SpawnAssetRequest(Request) AIOnOff=false end - -- Create an empty set. + -- Create an empty group set. local _groupset=SET_GROUP:New():FilterDeads() - -- Spawn the assets. - local _spawngroups={} + -- Table for all spawned assets. local _assets={} -- Loop over cargo requests. @@ -1522,7 +1575,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Spawn ground troops. --_group=_spawn:SpawnFromCoordinate(spawncoord) - _group=self:_SpawnGroundAsset(_assetitem, Request) + _group=self:_SpawnAssetGround(_assetitem, Request) elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then @@ -1530,7 +1583,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Spawn air units. --_group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) - _group=self:_SpawnAssetAircraft(_assetitem, Request) + _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[i]) elseif _assetitem.category==Group.Category.TRAIN then @@ -1542,6 +1595,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) end + -- Add group to group set and asset list. if _group then _groupset:AddGroup(_group) table.insert(_assets, _assetitem) @@ -1552,8 +1606,10 @@ function WAREHOUSE:_SpawnAssetRequest(Request) end -- Delete spawned items from warehouse stock. - for _,_item in pairs(_assets) do - self:_DeleteStockItem(_item) + for _,_asset in pairs(_assets) do + local asset=_asset --#WAREHOUSE.Stockitem + Request.assets[asset.uid]=asset + self:_DeleteStockItem(asset) end return _groupset,_assets @@ -1580,8 +1636,9 @@ function WAREHOUSE:onafterUnloaded(From, Event, To, group) local speedmax=group:GetSpeedMax() if group:IsGround() then + -- Route group to spawn zone. if speedmax>1 then - group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(50), speedmax*0.5, AI.Task.VehicleFormation.RANK, 3) + group:RouteGroundTo(self.spawnzone:GetRandomCoordinate(), speedmax*0.5, AI.Task.VehicleFormation.RANK, 3) else -- Immobile ground unit ==> directly put it into the warehouse. self:Arrived(group) @@ -1626,9 +1683,8 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) group:RouteGroundTo(request.warehouse.coordinate, group:GetSpeedMax()*0.3, "Off Road") end - -- Move asset into new warehouse. - -- TODO: need to figure out which template group name I best take. - request.warehouse:__AddAsset(60, group:GetName(), 1) + -- Move asset from pending queue into new warehouse. + request.warehouse:__AddAsset(60, self:_GetAssetFromGroupRequest(group,request), 1) -- All cargo delivered. if request and ncargo==0 then @@ -1639,6 +1695,20 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) end +--- Get asset from group and request. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group that has arrived at its destination. +-- @param #WAREHOUSE.Pendingitem request Pending request. +-- @return #WAREHOUSE.Stockitem The asset. +function WAREHOUSE:_GetAssetFromGroupRequest(group,request) + + -- Get the IDs for this group. In particular, we use the asset ID to figure out which group was delivered. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + -- Retrieve asset from request. + local asset=request.assets[aid] +end + --- Update the pending requests by removing assets that have arrived. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that has arrived at its destination. @@ -1671,7 +1741,7 @@ function WAREHOUSE:_UpdatePending(group) self:E(self.wid..string.format("WARNING: pending request could not be updated since request did not exist in pending queue!")) end - return request + return request,wid,aid,rid end @@ -1797,60 +1867,6 @@ function WAREHOUSE:_RouteGround(Group, Coordinate, Speed) end end ---- Route the airplane from one airbase another. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP Aircraft 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. --- @return Wrapper.Group#GROUP Group that was spawned by RAT. -function WAREHOUSE:_RouteAirRat(Aircraft, ToAirbase, Speed) - - if Aircraft and Aircraft:IsAlive()~=nil then - - -- Get parking data of all units. - local parkingdata={} - - local units=Aircraft:GetUnits() - for _,_unit in pairs(units) do - local unit=_unit --Wrapper.Unit#UNIT - local _spot,_terminal,_distance=unit:GetCoordinate():GetClosestOccupiedParkingSpot(self.airbase) - table.insert(parkingdata, {Coordinate=_spot, TerminalID=_terminal}) - end - env.info("FF parking data") - self:E(parkingdata) - - -- Create a RAT object to use its flight plan. - local rat=RAT:New(Aircraft:GetName()) - - -- Init some parameters. - rat:SetDeparture(self.airbase:GetName()) - rat:SetDestination(ToAirbase:GetName()) - --rat:SetCoalitionAircraft(color) - rat:SetCountry(self.country) - rat:NoRespawn() - - -- Init spawn but do not actually spawn. - rat:Spawn(0) - --rat:_SpawnWithRoute(_departure,_destination,_takeoff,_landing,_livery,_waypoint,_lastpos,_nrespawn,parkingdata) - - -- Destroy the original aircraft. - Aircraft:Destroy() - - -- Spawn RAT aircraft at specific parking sports. - local spawnindex=rat:_SpawnWithRoute(self.airbase:GetName(), ToAirbase:GetName(), RAT.wp.hot, nil, nil, nil, nil, nil, parkingdata) - - -- Get the group and check it's name. - local group=rat.ratcraft[spawnindex].group --Wrapper.Group#GROUP - self:E(self.wid..string.format("Spawned new RAT aircraft as group %s", group:GetName())) - - group:SmokeBlue() - -- Activate group. - local bla=group:SetCommand({id='Start', params={}}) - self:E({bla=bla}) - return group - end - -end --- Route the airplane from one airbase another. -- @param #WAREHOUSE self @@ -1916,9 +1932,6 @@ function WAREHOUSE:_RouteAir(Aircraft, ToAirbase, Speed) local newAC=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) env.info("FF Respawn at current airbase group = "..newAC:GetName().." name after") - -- Handle event engine shutdown and trigger delivered event. - -- Not this did not work unless the routine would retrive the state from get/set state! - --newAC:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) else self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(Aircraft:GetName()), tostring(Aircraft:IsAlive()))) end @@ -2653,12 +2666,12 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) local nunits=#group:GetUnits() local terminal=self:_GetTerminal(asset.attribute) - --[[ + env.info("asset name = "..tostring(asset.templatename)) env.info("asset attribute = "..tostring(asset.attribute)) env.info("terminal type = "..tostring(terminal)) + env.info("parking spots = "..tostring(nunits)) env.info("parking spots = "..tostring(#parkingdata)) - ]] -- Find appropiate parking spots for this group. local spots=self.airbase:FindFreeParkingSpotForAircraft(group, terminal, nil, nil, nil, nil, nil, nil, parkingdata) @@ -2667,7 +2680,7 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) if spot then local coord=spot.Coordinate --Core.Point#COORDINATE local text=string.format("Parking spot for %s:\nAsset id=%d, Terminal id=%d", asset.templatename, asset.uid, spot.TerminalID) - coord:MarkToAll(text) + --coord:MarkToAll(text) self:E(self.wid..text) end end @@ -2925,14 +2938,14 @@ function WAREHOUSE:_GetAttribute(groupname) attribute=WAREHOUSE.Attribute.TANKER elseif awacs then attribute=WAREHOUSE.Attribute.AWACS + elseif bomber then + attribute=WAREHOUSE.Attribute.BOMBER 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 @@ -3160,7 +3173,7 @@ function WAREHOUSE:_GetFlightplan(group,_departure,_destination) local AlphaDescent=math.rad(4) -- Expected cruise level (peak of Gaussian distribution) - local FLcruise_expect=200*RAT.unit.FL2m + local FLcruise_expect=150*RAT.unit.FL2m --- DEPARTURE AIRPORT @@ -3180,9 +3193,9 @@ function WAREHOUSE:_GetFlightplan(group,_departure,_destination) --- DESCENT/HOLDING POINT - -- Get a random point between 5 and 20 km away from the destination. - local Rhmin=8000 - local Rhmax=20000 + -- Get a random point between 5 and 10 km away from the destination. + local Rhmin=5000 + local Rhmax=10000 if _category==Group.Category.HELICOPTER then -- For helos we set a distance between 500 to 1000 m. Rhmin=500 @@ -3190,8 +3203,9 @@ function WAREHOUSE:_GetFlightplan(group,_departure,_destination) end -- Coordinates of the holding point. y is the land height at that point. - local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin) - local Pholding=COORDINATE:NewFromVec2(Vholding) + --local Vholding=Pdestination:GetRandomVec2InRadius(Rhmax, Rhmin) + --local Pholding=COORDINATE:NewFromVec2(Vholding) + local Pholding=Pdestination:GetRandomCoordinateInRadius(Rhmax, Rhmin) -- AGL height of holding point. local H_holding=Pholding.y @@ -3293,8 +3307,26 @@ function WAREHOUSE:_GetFlightplan(group,_departure,_destination) -- Distances. local d_climb = h_climb/math.tan(AlphaClimb) local d_descent = h_descent/math.tan(AlphaDescent) - local d_cruise = d_total-d_climb-d_descent + local d_cruise = d_total-d_climb-d_descent + -- Debug. + local text=string.format("Flight plan:\n") + text=text..string.format("Vx max = %d\n", Vmax) + text=text..string.format("Vx climb = %d\n", VxClimb) + text=text..string.format("Vx cruise = %d\n", VxCruise) + text=text..string.format("Vx descent = %d\n", VxDescent) + text=text..string.format("Vx holding = %d\n", VxHolding) + text=text..string.format("Vx final = %d\n", VxFinal) + text=text..string.format("Dist climb = %d\n", d_climb) + text=text..string.format("Dist cruise = %d\n", d_cruise) + text=text..string.format("Dist descent = %d\n", d_descent) + text=text..string.format("Dist total = %d\n", d_total) + text=text..string.format("FL min = %d\n", FLmin) + text=text..string.format("FL cruise * = %d\n", FLcruise) + text=text..string.format("FL max = %d\n", FLmax) + text=text..string.format("Ceiling = %d\n", ceiling) + env.info(text) + -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. if d_cruise<0 then d_cruise=100 @@ -3307,52 +3339,44 @@ function WAREHOUSE:_GetFlightplan(group,_departure,_destination) --- Departure/Take-off c[#c+1]=Pdeparture wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true,_departure, nil, "Departure") - --wp[#wp+1]=self:_Waypoint(#wp+1, "Departure", takeoff, c[#wp+1], VxClimb, H_departure, departure) --- Climb local Pclimb=Pdeparture:Translate(d_climb/2, heading) Pclimb.y=H_departure+(FLcruise-H_departure)/2 c[#c+1]=Pclimb wp[#wp+1]=Pclimb:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxClimb, true, nil, nil, "Climb") - --wp[#wp+1]=self:_Waypoint(#wp+1, "Climb", RAT.wp.climb, c[#wp+1], VxClimb, ) --- Begin of Cruise local Pcruise1=Pclimb:Translate(d_climb/2, heading) Pcruise1.y=FLcruise c[#c+1]=Pcruise1 wp[#wp+1]=Pcruise1:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Begin of Cruise") - --wp[#wp+1]=self:_Waypoint(#wp+1, "Begin of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) --- End of Cruise local Pcruise2=Pcruise1:Translate(d_cruise, heading) Pcruise2.y=FLcruise c[#c+1]=Pcruise2 wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "End of Cruise") - --wp[#wp+1]=self:_Waypoint(#wp+1, "End of Cruise", RAT.wp.cruise, c[#wp+1], VxCruise, FLcruise) --- Descent local Pdescent=Pcruise2:Translate(d_descent/2, heading) Pdescent.y=FLcruise-(FLcruise-(h_holding+H_holding))/2 c[#c+1]=Pdescent wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent") - --wp[#wp+1]=self:_Waypoint(#wp+1, "Descent", RAT.wp.descent, c[#wp+1], VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) --- Holding point Pholding.y=H_holding+h_holding c[#c+1]=Pholding wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding") - --wp[#wp+1]=self:_Waypoint(#wp+1, "Holding Point", RAT.wp.holding, c[#wp+1], VxHolding, H_holding+h_holding) --- Final destination. c[#c+1]=Pdestination - wp[#wp+1]=Pcruise2:WaypointAir("BARO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, nil, nil, "Final Destination") - --wp[#wp+1]=self:_Waypoint(#wp+1, "Final Destination", landing, c[#wp+1], VxFinal, H_destination, destination) + wp[#wp+1]=Pcruise2:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, _destination, nil, "Final Destination") return wp,c end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 10174dd5b..34d76c792 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1453,7 +1453,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- SpawnPoint.airdromeId = AirbaseID end - SpawnPoint.alt = AirbaseCoord:GetLandHeight() + SpawnPoint.type = GROUPTEMPLATE.Takeoff[Takeoff][1] -- type SpawnPoint.action = GROUPTEMPLATE.Takeoff[Takeoff][2] -- action @@ -1474,7 +1474,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- -- Get unit coordinates for respawning position. local uc=unit:GetCoordinate() - + uc:MarkToAll(string.format("re-spawnplace %s terminal %d", unit:GetName(), TermialID)) SpawnTemplate.units[UnitID].x = uc.x --Parkingspot.x SpawnTemplate.units[UnitID].y = uc.z --Parkingspot.z @@ -1483,24 +1483,26 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- SpawnTemplate.units[UnitID].parking = TermialID SpawnTemplate.units[UnitID].parking_id = nil - if UnitID==1 then - x=uc.x - y=uc.z - end - + --SpawnTemplate.units[UnitID].unitId=nil end - SpawnPoint.x = x --AirbaseCoord.x - SpawnPoint.y = y --AirbaseCoord.z + --SpawnTemplate.groupId=nil - SpawnTemplate.x = x --AirbaseCoord.x - SpawnTemplate.y = y --AirbaseCoord.z + SpawnPoint.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x + SpawnPoint.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z + SpawnPoint.alt = SpawnTemplate.units[1].alt --AirbaseCoord:GetLandHeight() + + SpawnTemplate.x = SpawnTemplate.units[1].x --x --AirbaseCoord.x + SpawnTemplate.y = SpawnTemplate.units[1].y --y --AirbaseCoord.z -- Set uncontrolled state. SpawnTemplate.uncontrolled=Uncontrolled - -- Destroy and respawn. + -- Destroy old group. self:Destroy() + + + --SCHEDULER:New(nil, DATABASE.Spawn, {_DATABASE, SpawnTemplate}, 0.00001) _DATABASE:Spawn( SpawnTemplate ) -- Reset events. From f9b4eeef67025c8a009508e171f76b5db260f87b Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 20 Aug 2018 16:29:04 +0200 Subject: [PATCH 19/73] Warehouse v0.2.2w --- .../Moose/Functional/Warehouse.lua | 384 ++++++++++++------ 1 file changed, 263 insertions(+), 121 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index a602549a3..e1fb8d8b2 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -39,7 +39,7 @@ -- @field #number markerid ID of the warehouse marker at the airbase. -- @field #number dTstatus Time interval in seconds of updating the warehouse status and processing new events. Default 30 seconds. -- @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 stock Table holding all assets in stock. Table entries are of type @{#WAREHOUSE.Assetitem}. -- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. -- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table entries are of type @{#WAREHOUSE.Pendingitem}. -- @extends Core.Fsm#FSM @@ -113,7 +113,7 @@ WAREHOUSE = { } --- Item of the warehouse stock table. --- @type WAREHOUSE.Stockitem +-- @type WAREHOUSE.Assetitem -- @field #number uid Unique id of the asset. -- @field #string templatename Name of the template group. -- @field #table template The spawn template of the group. @@ -121,7 +121,9 @@ WAREHOUSE = { -- @field #string unittype Type of the first unit of the group as obtained by the Object.getTypeName() DCS API function. -- @field #number nunits Number of units in the group. -- @field #number range Range of the unit in meters. +-- @field #number size Maximum size in length and with of the asset in meters. -- @field #number speedmax Maximum speed in km/h the unit can do. +-- @field DCS#Object.Desc DCSdesc All DCS descriptors. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. --- Item of the warehouse queue table. @@ -140,7 +142,7 @@ WAREHOUSE = { --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem -- @extends #WAREHOUSE.Queueitem --- @field #table assets Table of assets - cargo or transport. Each element of the table is a @{#WAREHOUSE.Stockitem}. +-- @field #table assets Table of assets - cargo or transport. Each element of the table is a @{#WAREHOUSE.Assetitem}. -- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. -- @field #number ndelivered Number of groups delivered to destination. -- @field #number cargoattribute Attribute of cargo assets of type @{#WAREHOUSE.Attribute}. @@ -150,19 +152,23 @@ WAREHOUSE = { -- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. -- @field #number ntransporthome Number of transports back home. transportattribute ---- Descriptors enumerator describing the type of the asset in stock. +--- Descriptors enumerator describing the type of the asset. -- @type WAREHOUSE.Descriptor +-- @field #string TEMPLATENAME Name of the asset template. +-- @field #string UNITTYPE Typename of the DCS unit. +-- @field #string ATTRIBUTE Generalized attribute @{#WAREHOUSE.Attribute}. +-- @field #string CATEGORY Asset category of type DCS#Group.Category, i.e. GROUND, AIRPLANE, HELICOPTER, SHIP, TRAIN. WAREHOUSE.Descriptor = { - ID="id", + --ID="id", TEMPLATENAME="templatename", - CATEGORY="category", UNITTYPE="unittype", ATTRIBUTE="attribute", + CATEGORY="category", } --- Warehouse generalited categories. -- @type WAREHOUSE.Attribute --- @field #string TRANSPORT_PLANE Airplane with transport capability. Usually bigger. +-- @field #string TRANSPORT_PLANE Airplane with transport capability. Usually bigger, i.e. needs larger airbases and parking spots. -- @field #string TRANSPORT_HELO Helicopter with transport capability. WAREHOUSE.Attribute = { TRANSPORT_PLANE="Transport_Plane", @@ -193,9 +199,18 @@ WAREHOUSE.TransportType = { SELFPROPELLED = "Selfpropelled", } +--- Warehouse database. Note that this is a global array to have easier exchange between warehouses. +-- @type WAREHOUSE.db +-- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added. +-- @field #table Assets Table holding registered assets, which are of type @{Functional.Warehouse#WAREHOUSE.Assetitem}. +WAREHOUSE.db = { + AssetID = 0, + Assets = {}, +} + --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.2" +WAREHOUSE.version="0.2.2w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -220,6 +235,7 @@ WAREHOUSE.version="0.2.2" -- TODO: Add general message function for sending to coaliton or debug. -- TODO: Use RAT for routing air units. Should be possible but might need some modifications of RAT, e.g. explit spawn place. But flight plan should be better. -- TODO: Can I make a request with specific assets? E.g., once delivered, make a request for exactly those assests that were in the original request. +-- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -515,7 +531,7 @@ function WAREHOUSE:IsAttacked() return self:is("Attacked") end ---- Check if the warehouse has a road connection to another warehouse. Both warehouses need to be running! +--- Check if the warehouse has a road connection to another warehouse. Both warehouses need to be started! -- @param #WAREHOUSE self -- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. -- @param #boolean markpath If true, place markers of path segments on the F10 map. @@ -535,6 +551,26 @@ function WAREHOUSE:HasConnectionRoad(warehouse, markpath, smokepath) return nil, -1 end +--- Check if the warehouse has a railroad connection to another warehouse. Both warehouses need to be started! +-- @param #WAREHOUSE self +-- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. +-- @param #boolean markpath If true, place markers of path segments on the F10 map. +-- @param #boolean smokepath If true, put green smoke on path segments. +-- @return #boolean If true, the two warehouses are connected by road. +-- @return #number Path length in meters. Negative distance -1 meter indicates no connection. +function WAREHOUSE:HasConnectionRail(warehouse, markpath, smokepath) + if warehouse then + if self.rail and warehouse.rail then + local _,length,gotpath=self.road:GetPathOnRoad(warehouse.road, false, true, markpath, smokepath) + return gotpath, length or -1 + else + -- At least one of the warehouses has no rail connection. + return false, -1 + end + end + return nil, -1 +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -568,8 +604,7 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Debug mark warehouse & spawn zone. self.zone:BoundZone(30, self.country) self.spawnzone:BoundZone(30, self.country) - - --self.spawnzone:GetCoordinate():MarkToAll("Spawnzone of warehouse "..self.alias) + -- Get the closest point on road wrt spawnzone of ground assets. local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad() @@ -625,7 +660,7 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterStop(From, Event, To) - self:E(self.wid..string.format("Warehouse stopped")) + self:E(self.wid..string.format("Warehouse stopped!")) -- Unhandle event. self:UnHandleEvent(EVENTS.Birth) @@ -637,6 +672,9 @@ function WAREHOUSE:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.BaseCaptured) + -- Clear all pending schedules. + self.CallScheduler:Clear() + end --- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. @@ -645,7 +683,7 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterPause(From, Event, To) - self:E(self.wid..string.format("Warehouse paused! Queued requests are not processed in this state.")) + self:E(self.wid..string.format("Warehouse %s paused! Queued requests are not processed in this state.", self.alias)) end --- On after "Unpause" event. Unpauses the warehouse, i.e. requests in queue are processed again. @@ -665,7 +703,7 @@ end -- @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 %s", self.alias)) + self:E(self.wid..string.format("Checking status of warehouse %s.", self.alias)) -- Print status. self:_DisplayStatus() @@ -717,61 +755,90 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) -- Set default. local n=ngroups or 1 - local asset --Wrapper.Group#GROUP - if type(group)=="string" then - env.info("New asset passed as string") - asset=GROUP:FindByName(group) - elseif type(group)=="table" and group.aid~=nil then - env.info("New asset passed as asset") - self:_AddExistingAsset(group) - return - elseif group:IsInstanceOf("GROUP") then - env.info("New asset passed as string") - asset=group - else - self:E("ERROR: Invalid asset added!") - return nil - end - - - -- Check if group exists and has a DCS object. - -- TODO: Need to check this carefully if this words with CARGO etc. - if asset and asset:IsAlive()~=nil and asset:GetDCSObject() then - - -- Add new asset. - self:_AddNewAsset(asset, ngroups) + if group then + + -- Get unique ids from group name. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + -- Check if this is an known or a new asset group. + + if aid~=nil and wid~=nil then + -- We got a warehouse and asset id ==> this is an "old" group. + local asset=self:_FindAssetInDB(group) + + -- Note the group is only added once, i.e. the ngroups parameter is ignored here. + -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. + if asset~=nil then + table.insert(self.stock, asset) + end + + else + + -- This is a group that is not in the db yet. Add it n times. + local assets=self:_RegisterAsset(group, n) + + -- Add created assets to stock of this warehouse. + for _,asset in pairs(assets) do + table.insert(self.stock, asset) + end + end -- Destroy group if it is alive. + -- TODO: This causes a problem, when a completely new asset is added, i.e. not from a template group. + -- Need to create a "zombie" template group maybe? if group:IsAlive()==true then group:Destroy() 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 - + end ---- Add an existing asset to the warehouse stock. +--- Find an asset in the the global warehouse db. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Stockitem asset The asset to be added. -function WAREHOUSE:_AddExistingAsset(asset) - table.insert(self.stock, asset) +-- @param Wrapper.Group#GROUP group The group from which it is assumed that it has a registered asset. +-- @return #WAREHOUSE.Assetitem The asset from the data base or nil if it could not be found. +function WAREHOUSE:_FindAssetInDB(group) + -- Get unique ids from group name. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + if aid~=nil then + local asset=WAREHOUSE.db.Assets[aid] + if asset==nil then + self:E(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName())) + end + return asset + end + + self:E(string.format("ERROR: Group %s does not contain an asset ID in its name!", group:GetName())) + return nil end ---- Add a completely new asset to the warehouse. +--- Register new asset in globase warehouse data base. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that will be added to the warehouse stock. -- @param #number ngroups Number of groups to be added. -function WAREHOUSE:_AddNewAsset(group, ngroups) +-- @return #table A table containing all registered assets. +function WAREHOUSE:_RegisterAsset(group, ngroups) -- Set default. local n=ngroups or 1 + -- Get the size of an object. + local function _GetObjectSize(DCSdesc) + if DCSdesc.box then + local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) + local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height + local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) + return math.max(x,z), x , y, z + end + return 0,0,0,0 + end + local templategroupname=group:GetName() local DCSgroup=group:GetDCSObject() + local DCSunit=DCSgroup:getUnit(1) local DCSdesc=DCSunit:getDesc() local DCSdisplay=DCSdesc.displayName @@ -779,45 +846,61 @@ function WAREHOUSE:_AddNewAsset(group, ngroups) local DCStype=DCSunit:getTypeName() local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() - group:GetCategory() - - env.info(string.format("New asset for warehouse %s:", self.alias)) - 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)) - env.info(string.format("Speed max = %s km/h", tostring(SpeedMax))) - env.info(string.format("Range min = %s m", tostring(RangeMin))) - self:E({fullassetdesc=DCSdesc}) + local smax,sx,sy,sz=_GetObjectSize(DCSdesc) -- Get the generalized attribute. local attribute=self:_GetAttribute(templategroupname) + -- Table for returned assets. + local assets={} + -- Add this n times to the table. for i=1,n do - local stockitem={} --#WAREHOUSE.Stockitem + local asset={} --#WAREHOUSE.Assetitem -- Increase asset unique id counter. self.assetid=self.assetid+1 + WAREHOUSE.db.AssetID=WAREHOUSE.db.AssetID+1 -- Set parameters. - stockitem.uid=self.assetid - stockitem.templatename=templategroupname - stockitem.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) - stockitem.category=DCScategory - stockitem.unittype=DCStype - stockitem.attribute=attribute - stockitem.range=RangeMin - stockitem.speedmax=SpeedMax + asset.uid=WAREHOUSE.db.AssetID + asset.templatename=templategroupname + asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) + asset.category=DCScategory + asset.unittype=DCStype + asset.nunits=#asset.template.units + asset.attribute=attribute + asset.range=RangeMin + asset.speedmax=SpeedMax + asset.size=smax + asset.DCSdesc=DCSdesc - -- Modify the template so that the group is spawned with the right coalition. - -- TODO: somehow this is now acknoleged properly. Found a workaround however with SPAWN AIP functions. - stockitem.template.CoalitionID=self.coalition - stockitem.template.CountryID=self.country + if i==1 then + env.info(string.format("New asset for warehouse %s:", self.alias)) + 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)) + env.info(string.format("Size max = %s", asset.size)) + env.info(string.format("Speed max = %s km/h", SpeedMax)) + env.info(string.format("Range min = %s m", RangeMin)) + self:E({fullassetdesc=DCSdesc}) + end - table.insert(self.stock, stockitem) + -- Add asset to global db. + table.insert(WAREHOUSE.db.Assets, {uid=asset.uid, asset=asset}) + + table.insert(assets,asset) end + return assets +end + +--- Asset item characteristics. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Assetitem asset +function WAREHOUSE:_AssetItemInfo(asset) + end --- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. @@ -831,7 +914,7 @@ end --- Spawn a ground asset in the spawnzone of the warehouse. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. function WAREHOUSE:_SpawnAssetGround(asset, request) @@ -887,7 +970,7 @@ end --- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. @@ -900,8 +983,9 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking) -- Set and empty route. if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - --template.route.points=self:_GetFlightplan(group,_departure,_destination) - template.route.points[1] = {} + -- Get flight path. + template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) + --template.route.points[1] = {} else template.route.points[1] = {} end @@ -1013,9 +1097,9 @@ end --- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Stockitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. --- @return #template Prepared new spawn template. +-- @return #table Prepared new spawn template. function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, request) -- Create an own copy of the template! @@ -1127,7 +1211,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Check if destination is in range for all requested assets. for _,_asset in pairs(_assets) do - local asset=_asset --#WAREHOUSE.Stockitem + local asset=_asset --#WAREHOUSE.Assetitem -- Check if destination is in range. if asset.range Date: Mon, 20 Aug 2018 23:05:35 +0200 Subject: [PATCH 20/73] Warehouse v0.2.3 --- Moose Development/Moose/Core/Settings.lua | 2 +- .../Moose/Functional/Warehouse.lua | 154 ++++++++++++------ 2 files changed, 107 insertions(+), 49 deletions(-) diff --git a/Moose Development/Moose/Core/Settings.lua b/Moose Development/Moose/Core/Settings.lua index 819690607..d5909452d 100644 --- a/Moose Development/Moose/Core/Settings.lua +++ b/Moose Development/Moose/Core/Settings.lua @@ -364,7 +364,7 @@ do -- SETTINGS -- @param #SETTINGS self -- @return #boolean true if BRA function SETTINGS:IsA2A_BRAA() - self:E( { BRA = ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) } ) + self:T( { BRA = ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) } ) return ( self.A2ASystem and self.A2ASystem == "BRAA" ) or ( not self.A2ASystem and _SETTINGS:IsA2A_BRAA() ) end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e1fb8d8b2..cf04abcca 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -210,7 +210,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.2w" +WAREHOUSE.version="0.2.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -233,7 +233,7 @@ WAREHOUSE.version="0.2.2w" -- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? -- TODO: Handle cargo crates. -- TODO: Add general message function for sending to coaliton or debug. --- TODO: Use RAT for routing air units. Should be possible but might need some modifications of RAT, e.g. explit spawn place. But flight plan should be better. +-- NOGO: Use RAT for routing air units. Should be possible but might need some modifications of RAT, e.g. explit spawn place. But flight plan should be better. -- TODO: Can I make a request with specific assets? E.g., once delivered, make a request for exactly those assests that were in the original request. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. @@ -365,14 +365,14 @@ function WAREHOUSE:New(warehouse, alias) --- Trigger the FSM event "AddAsset". Add an airplane group to the warehouse stock. -- @function [parent=#WAREHOUSE] AddAsset -- @param #WAREHOUSE self - -- @param #string templategroupname Name of the late activated template group as defined in the mission editor. + -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. --- Trigger the FSM event "AddAsset" with a delay. Add an airplane group to the warehouse stock. -- @function [parent=#WAREHOUSE] __AddAsset -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. - -- @param #string templategroupname Name of the late activated template group as defined in the mission editor. + -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. @@ -704,6 +704,7 @@ end -- @param #string To To state. function WAREHOUSE:onafterStatus(From, Event, To) self:E(self.wid..string.format("Checking status of warehouse %s.", self.alias)) + env.info(string.format("FF number of global assets = %d, current asset id = %d", #WAREHOUSE.db.Assets, WAREHOUSE.db.AssetID)) -- Print status. self:_DisplayStatus() @@ -712,8 +713,8 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:_CheckConquered() -- Print queue. - self:_PrintQueue(self.queue, "Queue:") - self:_PrintQueue(self.pending, "Pending:") + self:_PrintQueue(self.queue, "Queue waiting") + self:_PrintQueue(self.pending, "Queue pending") -- Check if requests are valid and remove invalid one. self:_CheckRequestConsistancy(self.queue) @@ -755,24 +756,38 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) -- Set default. local n=ngroups or 1 + -- Handle case where just a string is passed. + if type(group)=="string" then + group=GROUP:FindByName(group) + end + + env.info(string.format("Adding asset group %s", group:GetName())) + if group then -- Get unique ids from group name. local wid,aid,rid=self:_GetIDsFromGroup(group) -- Check if this is an known or a new asset group. - if aid~=nil and wid~=nil then + env.info(string.format("Adding known! asset group %s with id %d", group:GetName(), aid)) + -- We got a warehouse and asset id ==> this is an "old" group. local asset=self:_FindAssetInDB(group) + env.info(string.format("Adding known! asset group %s with id %d", group:GetName(), aid)) + -- Note the group is only added once, i.e. the ngroups parameter is ignored here. -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. if asset~=nil then + env.info(string.format("Adding new asset to stock. asset id = %d, attribute = %s", asset.uid, asset.attribute)) table.insert(self.stock, asset) + else + env.error("ERROR known asset could not be found in global warehouse db!") end else + env.info("Asset unkonwn ==> registering in DB!") -- This is a group that is not in the db yet. Add it n times. local assets=self:_RegisterAsset(group, n) @@ -787,6 +802,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) -- TODO: This causes a problem, when a completely new asset is added, i.e. not from a template group. -- Need to create a "zombie" template group maybe? if group:IsAlive()==true then + env.info("FF destorying group "..group:GetName()) group:Destroy() end @@ -799,11 +815,14 @@ end -- @param Wrapper.Group#GROUP group The group from which it is assumed that it has a registered asset. -- @return #WAREHOUSE.Assetitem The asset from the data base or nil if it could not be found. function WAREHOUSE:_FindAssetInDB(group) + -- Get unique ids from group name. local wid,aid,rid=self:_GetIDsFromGroup(group) if aid~=nil then + local asset=WAREHOUSE.db.Assets[aid] + self:E({asset=asset}) if asset==nil then self:E(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName())) end @@ -827,9 +846,9 @@ function WAREHOUSE:_RegisterAsset(group, ngroups) -- Get the size of an object. local function _GetObjectSize(DCSdesc) if DCSdesc.box then - local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) + local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) --length local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height - local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) + local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) --width return math.max(x,z), x , y, z end return 0,0,0,0 @@ -876,20 +895,13 @@ function WAREHOUSE:_RegisterAsset(group, ngroups) asset.DCSdesc=DCSdesc if i==1 then - env.info(string.format("New asset for warehouse %s:", self.alias)) - 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)) - env.info(string.format("Size max = %s", asset.size)) - env.info(string.format("Speed max = %s km/h", SpeedMax)) - env.info(string.format("Range min = %s m", RangeMin)) - self:E({fullassetdesc=DCSdesc}) + self:_AssetItemInfo(asset) end -- Add asset to global db. - table.insert(WAREHOUSE.db.Assets, {uid=asset.uid, asset=asset}) + WAREHOUSE.db.Assets[asset.uid]=asset + -- Add asset to the table that is retured. table.insert(assets,asset) end @@ -900,7 +912,19 @@ end -- @param #WAREHOUSE self -- @param #WAREHOUSE.Assetitem asset function WAREHOUSE:_AssetItemInfo(asset) - + -- Info about asset: + local text=string.format("\nNew asset with id=%d for warehouse %s:\n", asset.uid, self.alias) + text=text..string.format("Template name = %s\n", asset.templatename) + text=text..string.format("Unit type = %s\n", asset.unittype) + text=text..string.format("Attribute = %s\n", asset.attribute) + text=text..string.format("Category = %d\n", asset.category) + text=text..string.format("Units # = %d\n", asset.nunits) + text=text..string.format("Size max = %5.2f m\n", asset.size) + text=text..string.format("Speed max = %5.2f km/h\n", asset.speedmax) + text=text..string.format("Range max = %5.2f km\n", asset.range/1000) + self:E(self.wid..text) + self:E({DCSdesc=asset.DCSdesc}) + self:E({Template=asset.template}) end --- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. @@ -1871,12 +1895,13 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) --group:SmokeGreen() end - -- Remove pending request unless warehouse is under attack in which case we assume + -- Add a "defender request" to be able to despawn all assets once defeated. if self:IsAttacked() then - self.defenderrequest=request - self:_DeleteQueueItem(request, self.pending) + self.defenderrequest=request end + -- Remove pending request. + self:_DeleteQueueItem(request, self.pending) end --- On after "Attacked" event. Warehouse is under attack by an another coalition. @@ -1899,13 +1924,30 @@ end -- @param #string To To state. function WAREHOUSE:onafterDefeated(From, Event, To) self:E(self.wid..string.format("Attack was defeated!")) - --TODO Put all ground assets back in stock? How to remember which? Request id. Don't delete from pending? - local request=self.defenderrequest --#WAREHOUSE.Pendingitem - for _,group in pairs(request.cargogroupset:GetSetObjects()) do - -- Add assets back to stock. - self:__AddAsset(1,group) + + if self.defenderrequest then + + -- Get all assets that were deployed for defending the warehouse. + local request=self.defenderrequest --#WAREHOUSE.Pendingitem + + -- Route defenders back to warehoue (for visual reasons only) and put them back into stock. + for _,_group in pairs(request.cargogroupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + + -- Get max speed of group and route it back slowly to the warehouse. + local speed=group:GetSpeedMax() + if group:IsGround() and speed>1 then + group:RouteGroundTo(self.coordinate, speed*0.3) + end + + -- Add asset group back to stock after 60 seconds. + self:__AddAsset(60, group) + end + + -- Set defender request back to nil. + self.defenderrequest=nil + end - self.defenderrequest=nil end --- On after "Captured" event. Warehouse has been captured by another coalition. @@ -2139,13 +2181,13 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBirth(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event birth!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event birth!", self.alias)) - if EventData and EventData.id==world.event.S_EVENT_BIRTH then - if EventData.IniGroup then - local group=EventData.IniGroup - --local asset=self:_FindAssetInDB(group) - --if asset. + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:E(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s", self.alias, EventData.IniUnitName)) end end end @@ -2255,6 +2297,7 @@ function WAREHOUSE:_CheckConquered() -- Scan units in zone. --TODO: need to check if scan radius does what it should! + -- It seems to return units that are further away than the radius. local gotunits,_,_,units,_,_=coord:ScanObjects(radius, true, false, false) local Nblue=0 @@ -2270,13 +2313,19 @@ function WAREHOUSE:_CheckConquered() for _,_unit in pairs(units) do local unit=_unit --Wrapper.Unit#UNIT - -- Filter only groud units. - if unit:IsGround() then + local distance=coord:Get2DDistance(unit:GetCoordinate()) + + -- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances. + if unit:IsGround() and unit:IsAlive() and distance<= radius then -- Get coalition and country. local _coalition=unit:GetCoalition() local _country=unit:GetCountry() + -- Debug info. + self:E(self.wid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(), radius,_coalition,_country, distance)) + + -- Add up units for each side. if _coalition==coalition.side.BLUE then Nblue=Nblue+1 CountryBlue=_country @@ -2809,12 +2858,13 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) local nunits=#group:GetUnits() local terminal=self:_GetTerminal(asset.attribute) - - env.info("asset name = "..tostring(asset.templatename)) - env.info("asset attribute = "..tostring(asset.attribute)) - env.info("terminal type = "..tostring(terminal)) - env.info("parking spots = "..tostring(nunits)) - env.info("parking spots = "..tostring(#parkingdata)) + -- Debug info + env.info(string.format("Parking spot search:")) + env.info(string.format("Asset name = %s", asset.templatename)) + env.info(string.format("Asset attribute = %s", asset.attribute)) + env.info(string.format("Terminal type = %d", terminal)) + env.info(string.format("Unit number = %d", nunits)) + env.info(string.format("Parking spots = %d", #parkingdata)) -- Find appropiate parking spots for this group. local spots=self.airbase:FindFreeParkingSpotForAircraft(group, terminal, nil, nil, nil, nil, nil, nil, parkingdata) @@ -3173,13 +3223,21 @@ end -- @param #table queue Queue to print. -- @param #string name Name of the queue for info reasons. function WAREHOUSE:_PrintQueue(queue, name) - self:E(self.wid..name) + local text=string.format("%s at %s: ",name, self.alias) for _,_qitem in ipairs(queue) do local qitem=_qitem --#WAREHOUSE.Queueitem - local text=self.wid..string.format("UID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%s, Transport=%s, Ntransport=%d", - qitem.uid, qitem.prio, qitem.warehouse.alias, qitem.airbase:GetName(),qitem.category, qitem.assetdesc,tostring(qitem.assetdescval), tostring(qitem.nasset),qitem.transporttype,qitem.ntransport) - self:E(text) + -- Set airbase: + local airbasename="none" + if qitem.airbase then + airbasename=qitem.airbase:GetName() + end + local text=text..string.format("\nUID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%s, Transport=%s, Ntransport=%d.", + qitem.uid, qitem.prio, qitem.warehouse.alias, airbasename, qitem.category, qitem.assetdesc,tostring(qitem.assetdescval), tostring(qitem.nasset), qitem.transporttype, qitem.ntransport) end + if #queue==0 then + text=text.."Empty." + end + self:E(self.wid..text) end --- Display status of warehouse. @@ -3282,7 +3340,7 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) local Range=asset.range local _category=asset.category local ceiling=asset.DCSdesc.Hmax - local Vymax=asset.DCSDesc.VyMax + local Vymax=asset.DCSdesc.VyMax -- Max cruise speed 90% of max speed. local VxCruiseMax=0.90*Vmax From 31a5bfee9e35dcbda6480c5b49440ece214cc254 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 21 Aug 2018 16:38:55 +0200 Subject: [PATCH 21/73] Warehouse 0.2.3w --- .../Moose/Functional/Warehouse.lua | 350 +++++++++++++----- 1 file changed, 257 insertions(+), 93 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cf04abcca..32fdb9cc0 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -38,7 +38,7 @@ -- @field #number uid Unit identifier of the warehouse. Derived from the associated airbase. -- @field #number markerid ID of the warehouse marker at the airbase. -- @field #number dTstatus Time interval in seconds of updating the warehouse status and processing new events. Default 30 seconds. --- @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 #number queueid Unit id of each request in the queue. Essentially a running number starting at one and incremented when a new request is added. -- @field #table stock Table holding all assets in stock. Table entries are of type @{#WAREHOUSE.Assetitem}. -- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. -- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table entries are of type @{#WAREHOUSE.Pendingitem}. @@ -105,7 +105,6 @@ WAREHOUSE = { uid = nil, markerid = nil, dTstatus = 30, - assetid = 0, queueid = 0, stock = {}, queue = {}, @@ -210,7 +209,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.3" +WAREHOUSE.version="0.2.3w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -673,8 +672,7 @@ function WAREHOUSE:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.BaseCaptured) -- Clear all pending schedules. - self.CallScheduler:Clear() - + self.CallScheduler:Clear() end --- On after "Pause" event. Pauses the warehouse, i.e. no requests are processed. However, new requests and new assets can be added in this state. @@ -703,8 +701,8 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterStatus(From, Event, To) - self:E(self.wid..string.format("Checking status of warehouse %s.", self.alias)) - env.info(string.format("FF number of global assets = %d, current asset id = %d", #WAREHOUSE.db.Assets, WAREHOUSE.db.AssetID)) + self:E(self.wid..string.format("Checking status of warehouse %s. Current FSM state %s. Global warehouse asssets = %d.", self.alias, self:GetState(), #WAREHOUSE.db.Assets)) + --env.info(string.format("FF number of global assets = %d, current asset id = %d", #WAREHOUSE.db.Assets, WAREHOUSE.db.AssetID)) -- Print status. self:_DisplayStatus() @@ -713,14 +711,15 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:_CheckConquered() -- Print queue. - self:_PrintQueue(self.queue, "Queue waiting") - self:_PrintQueue(self.pending, "Queue pending") + self:_PrintQueue(self.queue, "Queue waiting - before request") + self:_PrintQueue(self.pending, "Queue pending - before request") -- Check if requests are valid and remove invalid one. self:_CheckRequestConsistancy(self.queue) -- If warehouse is running than requests can be processed. if self:IsRunning() or self:IsAttacked() then + -- Check queue and handle requests if possible. local request=self:_CheckQueue() @@ -728,6 +727,11 @@ function WAREHOUSE:onafterStatus(From, Event, To) if request then self:Request(request) end + + -- Print queue after processing requests. + self:_PrintQueue(self.queue, "Queue waiting - after request") + self:_PrintQueue(self.pending, "Queue pending - after request") + end -- Update warhouse marker on F10 map. @@ -761,7 +765,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) group=GROUP:FindByName(group) end - env.info(string.format("Adding asset group %s", group:GetName())) + self:E(string.format("Adding %d assets of group %s.", n, group:GetName())) if group then @@ -770,24 +774,20 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) -- Check if this is an known or a new asset group. if aid~=nil and wid~=nil then - env.info(string.format("Adding known! asset group %s with id %d", group:GetName(), aid)) -- We got a warehouse and asset id ==> this is an "old" group. local asset=self:_FindAssetInDB(group) - env.info(string.format("Adding known! asset group %s with id %d", group:GetName(), aid)) - -- Note the group is only added once, i.e. the ngroups parameter is ignored here. -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. if asset~=nil then - env.info(string.format("Adding new asset to stock. asset id = %d, attribute = %s", asset.uid, asset.attribute)) + self:E(string.format("Adding new asset with id = %d, attribute = %s to warehouse stock.", asset.uid, asset.attribute)) table.insert(self.stock, asset) else env.error("ERROR known asset could not be found in global warehouse db!") end else - env.info("Asset unkonwn ==> registering in DB!") -- This is a group that is not in the db yet. Add it n times. local assets=self:_RegisterAsset(group, n) @@ -878,7 +878,6 @@ function WAREHOUSE:_RegisterAsset(group, ngroups) local asset={} --#WAREHOUSE.Assetitem -- Increase asset unique id counter. - self.assetid=self.assetid+1 WAREHOUSE.db.AssetID=WAREHOUSE.db.AssetID+1 -- Set parameters. @@ -1036,12 +1035,7 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking) spawnpoint.helipadId = nil end - -- Get the right terminal type for this kind of aircraft. - --local terminaltype=self:_GetTerminal(asset.attribute) - - -- Get parking data. - --local parking=self.airbase:GetFreeParkingSpotsTable(terminaltype) - + -- Check enough parking spots. if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then --TODO Figure out what's necessary in this case. @@ -1421,7 +1415,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if _transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER then - Parking=self:_GetParkingForAssets(_assetstock) + Parking=self:_FindParkingForAssets(self.airbase,_assetstock) end -- Transport assets table. @@ -1440,12 +1434,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn with ALIAS here or DCS crashes! --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) local _alias=self:_Alias(_assetitem, Request) - - -- Spawn plane at airport in uncontrolled state. - local _takeoff=SPAWN.Takeoff.Cold - --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - local spawngroup=spawn:InitUnControlled(true):SpawnAtAirbase(self.airbase,_takeoff, nil, nil, false, _parking) + + -- Spawn plane at airport in uncontrolled state. + local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid]) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -1474,17 +1465,12 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - local _parking=Parking[i] -- Spawn with ALIAS here or DCS crashes! - --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) local _alias=self:_Alias(_assetitem, Request) - -- Spawn helo at airport. - local _takeoff=SPAWN.Takeoff.Hot - --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - local spawngroup=spawn:InitUnControlled(false):SpawnAtAirbase(self.airbase,_takeoff, nil, nil, false, _parking) + -- Spawn plane at airport in uncontrolled state. + local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid]) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -1523,10 +1509,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn with ALIAS here or DCS crashes! local _alias=self:_Alias(_assetitem, Request) - -- Spawn plane at airport in uncontrolled state. - --local spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias) - local spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - local spawngroup=spawn:SpawnFromCoordinate(self.spawnzone:GetRandomCoordinate()) + -- Spawn ground asset. + local spawngroup=self:_SpawnAssetGround(_assetitem, Request) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -1600,7 +1584,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) Carrier:SmokeRed() -- Add carrier back to warehouse stock. Actual unit is destroyed. - warehouse:AddAsset(Carrier, 1) + warehouse:AddAsset(Carrier) end @@ -1643,7 +1627,8 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_GetParkingForAssets(_assetstock) + Parking=self:_FindParkingForAssets(self.airbase,_assetstock) + --Parking=self:_GetParkingForAssets(_assetstock) end -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. @@ -1665,24 +1650,15 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - - -- Find a random point within the spawn zone. - local spawncoord=self.spawnzone:GetRandomCoordinate() -- Alias of the group. local _alias=self:_Alias(_assetitem, Request) - - -- Spawn object. Spawn with ALIAS here or DCS crashes! - --local _spawn=SPAWN:NewFromTemplate(_assetitem.template,_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country) - --local _spawn=SPAWN:NewWithAlias(_assetitem.templatename,_alias):InitCoalition(self.coalition):InitCountry(self.country):InitUnControlled(UnControlled):InitAIOnOff(AIOnOff) - local _group=nil --Wrapper.Group#GROUP - local _attribute=_assetitem.attribute - + -- Spawn an asset group. + local _group=nil --Wrapper.Group#GROUP if _assetitem.category==Group.Category.GROUND then -- Spawn ground troops. - --_group=_spawn:SpawnFromCoordinate(spawncoord) _group=self:_SpawnAssetGround(_assetitem, Request) elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then @@ -1690,7 +1666,6 @@ function WAREHOUSE:_SpawnAssetRequest(Request) --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! -- Spawn air units. - --_group=_spawn:SpawnAtAirbase(self.airbase, SPAWN.Takeoff.Cold, nil, nil, true, Parking[i]) _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[i]) elseif _assetitem.category==Group.Category.TRAIN then @@ -1708,7 +1683,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) _groupset:AddGroup(_group) table.insert(_assets, _assetitem) else - self:E(self.wid.."ERROR: cargo asset could not be spawned!") + self:E(self.wid.."ERROR: Cargo asset could not be spawned!") end end @@ -1792,7 +1767,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) end -- Move asset from pending queue into new warehouse. - request.warehouse:__AddAsset(60, group, 1) + request.warehouse:__AddAsset(60, group) -- All cargo delivered. if request and ncargo==0 then @@ -1913,7 +1888,8 @@ end -- @param DCS#country.id Country which is attacking the warehouse. function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) self:E(self.wid..string.format("Our warehouse is under attack!")) - --TODO: Spawn all ground units in the spawnzone? + + -- Spawn all ground units in the spawnzone? self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) end @@ -1969,6 +1945,17 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) end +--- On after "Destroyed" event. Warehouse was destroyed. Service is stopped. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterDestroyed(From, Event, To) + self:E(self.wid..string.format("Our warehouse was destroyed!")) + -- Stop warehouse FSM. + self:Stop() +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2187,7 +2174,7 @@ function WAREHOUSE:_OnEventBirth(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s", self.alias, EventData.IniUnitName)) + self:E(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s.", self.alias, EventData.IniUnitName)) end end end @@ -2196,21 +2183,45 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventEngineStartup(EventData) - self:E(self.wid..string.format("Warehouse %s captured event engine startup!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event engine startup!",self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:E(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end end --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventTakeOff(EventData) - self:E(self.wid..string.format("Warehouse %s captured event takeoff!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event takeoff!",self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:E(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end end --- Warehouse event handling function. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventLanding(EventData) - self:E(self.wid..string.format("Warehouse %s captured event landing!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event landing!",self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:E(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end end --- Warehouse event handling function. @@ -2232,8 +2243,7 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) local warehousename=self.warehouse:GetName() if EventData.IniUnitName==warehousename then env.info(self.wid..string.format("Warehouse %s alias %s was destroyed!", warehousename, self.alias)) - --TODO: Add destroy event. - self:__Stop(1) + self:Destroyed() end end @@ -2251,34 +2261,34 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) return end - if EventData and EventData.id==world.event.S_EVENT_BASE_CAPTURED then - if EventData.Place then + if EventData and EventData.Place then - -- Place is the airbase that was captured. - local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + -- Place is the airbase that was captured. + local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + + if EventData.PlaceName==self.airbasename then + -- Okay, this airbase belongs or did belong to this warehouse. - if EventData.PlaceName==self.airbasename then - -- Okay, this airbase belongs or did belong to this warehouse. - - -- New coalition of airbase after it was captured. - local coalitionAirbase=airbase:GetCoalition() - - -- So what can happen? - -- Warehouse is blue, airbase is blue and belongs to warehouse and red captures it ==> self.airbase=nil - -- Warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place - if self.airbase==nil then - -- Warehouse lost this airbase previously and not it was re-captured. - if coalitionAirbase == self.coalition then - self.airbase=airbase - end - else - -- Captured airbase belongs to this warehouse but was captured by other coaltion. - if coalitionAirbase ~= self.coalition then - self.airbase=nil - end + -- New coalition of airbase after it was captured. + local coalitionAirbase=airbase:GetCoalition() + + -- So what can happen? + -- Warehouse is blue, airbase is blue and belongs to warehouse and red captures it ==> self.airbase=nil + -- Warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place + if self.airbase==nil then + -- Warehouse lost this airbase previously and not it was re-captured. + env.info("FF airbase of warehouse is nil") + if coalitionAirbase == self.coalition then + self.airbase=airbase + env.info("FF air") + end + else + -- Captured airbase belongs to this warehouse but was captured by other coaltion. + if coalitionAirbase ~= self.coalition then + self.airbase=nil end - end + end end end @@ -2702,12 +2712,9 @@ function WAREHOUSE:_CheckRequestNow(request) local _assetattribute=_assets[1].attribute local _assetcategory=_assets[1].category - -- Check available parking for asset units. - local Parkingdata - local Parking + -- Check available parking for air asset units. if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then - Parkingdata=self.airbase:GetParkingSpotsTable() - Parking, Parkingdata=self:_GetParkingForAssets(_assets, Parkingdata) + local Parking=self:_FindParkingForAssets(self.airbase,_assets) if Parking==nil then local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) @@ -2741,7 +2748,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check available parking for transport units. if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then - Parking, Parkingdata=self:_GetParkingForAssets(_transports, Parkingdata) + local Parking=self:_FindParkingForAssets(self.airbase,_transports) if Parking==nil then local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) @@ -2893,6 +2900,148 @@ function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) return assetparking, parkingdata end +---------------- + +--- Seach unoccupied parking spots at the airbase for a list of assets. For each asset group a list of parking spots is returned. +-- During the search also the not yet spawned asset aircraft are considered. +-- If not enough spots for all asset units could be found, the routine returns nil! +-- @param #WAREHOUSE self +-- @param Wrapper.Airbase#AIRBASE airbase The airbase where we search for parking spots. +-- @param #table assets A table of assets for which the parking spots are needed. +-- @return #table Table of coordinates and terminal IDs of free parking spots. Each table entry has the elements .Coordinate and .TerminalID. +function WAREHOUSE:_FindParkingForAssets(airbase, assets) + + -- Init default + local scanradius=50 + local scanunits=true + local scanstatics=true + local scanscenery=false + local verysafe=false + + -- Function calculating the overlap of two (square) objects. + local function _overlap(l1,l2,dist) + local safedist=(l1/2+l2/2)*1.1 + local safe = (dist > safedist) + self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe))) + return safe + end + + -- Get parking spot data table. This contains all free and "non-free" spots. + local parkingdata=airbase:GetParkingSpotsTable() + + -- List of obstacles. + local obstacles={} + + -- Loop over all parking spots and get the obstacles. + -- TODO: How long does this take on very large airbases, i.e. those with hundereds of parking spots? + for _,parkingspot in pairs(parkingdata) do + + -- Coordinate of the parking spot. + local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE + local _termid=parkingspot.TerminalID + + -- Obstacles at or around this parking spot. + obstacles[_termid]={} + + -- Scan a radius of 50 meters around the spot. + local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) + + -- Check all units. + for _,_unit in pairs(_units) do + local unit=_unit --Wrapper.Unit#UNIT + local _coord=unit:GetCoordinate() + local _size=self:_GetObjectSize(unit:GetDCSObject()) + local _name=unit:GetName() + table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="unit"}) + end + + -- Check all statics. + for _,static in pairs(_statics) do + local _vec3=static:getPoint() + local _coord=COORDINATE:NewFromVec3(_vec3) + local _name=static:getName() + local _size=self:_GetObjectSize(static:GetDCSObject()) + table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="static"}) + end + + -- Check all scenery. + for _,scenery in pairs(_sceneries) do + local _vec3=scenery:getPoint() + local _coord=COORDINATE:NewFromVec3(_vec3) + local _name=scenery:getTypeName() + local _size=self:_GetObjectSize(scenery:GetDCSObject()) + table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="scenery"}) + end + + -- TODO Clients? Unoccupied client aircraft are also important! Are they already included in scanned units maybe? + + end + + -- Parking data for all assets. + local parking={} + + -- Loop over all assets that need a parking psot. + for _,asset in pairs(assets) do + + local _asset=asset --#WAREHOUSE.Assetitem + + local terminaltype=self:_GetTerminal(asset.attribute) + + -- Asset specific parking. + parking[_asset.uid]={} + + -- Loop over all units - each one needs a spot. + for i=1,_asset.nunits do + + -- Loop over all parking spots. + for _,parkingspot in pairs(parkingdata) do + + -- Check correct terminal type for asset. We don't want helos in shelters etc. + if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype) then + + -- Coordinate of the parking spot. + local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE + local _termid=parkingspot.TerminalID + + -- Loop over all obstacles. + local free=true + for _,obstacle in pairs(obstacles[_termid]) do + + -- Check if aircraft overlaps with any obstacle. + local safe=_overlap(_asset.size, obstacle.size, _spot:Get2DDistance(obstacle.coord)) + + -- Spot is blocked. + if not safe then + free=false + break + end + + end + + if free then + + -- Add parkingspot for this asset unit. + table.insert(parking[_asset.uid], parkingspot) + + -- Add the unit as obstacle so that this spot will not be available for the next unit. + -- TODO Alternatively, I could remove this parking spot from the table, right? + obstacles[_termid]={coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"} + + else + -- Not enough parking available! + return nil + end + + end -- check terminal type + end -- loop over parking spots + end -- loop over asset units + end -- loop over asset groups + + return parking +end + +----------------- + --- Get the request belonging to a group. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which the info is gathered. @@ -3154,6 +3303,21 @@ function WAREHOUSE:_GetAttribute(groupname) return attribute end +--- Size of the bounding box of a DCS object derived from the DCS descriptor table. If boundinb box is nil, a size of zero is returned. +-- @param #WAREHOUSE self +-- @param DCS#Object DCSobject The DCS object for which the size is needed. +-- @return #number Max size of object in meters. +function WAREHOUSE:_GetObjectSize(DCSobject) + local DCSdesc=DCSobject:getDesc() + if DCSdesc.box then + local x=DCSdesc.box.max.x+math.abs(DCSdesc.box.min.x) --length + local y=DCSdesc.box.max.y+math.abs(DCSdesc.box.min.y) --height + local z=DCSdesc.box.max.z+math.abs(DCSdesc.box.min.z) --width + return math.max(x,z), x , y, z + end + return 0,0,0,0 +end + --- Returns the number of assets for each generalized attribute. -- @param #WAREHOUSE self -- @param #table stock The stock of the warehouse. From 64e67494b6851b021837c6c37f885990fc8bb7ee Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 21 Aug 2018 23:58:22 +0200 Subject: [PATCH 22/73] Warehouse v0.2.4 --- .../Moose/Functional/Warehouse.lua | 83 +++++++++++++------ Moose Development/Moose/Wrapper/Airbase.lua | 23 ++++- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 32fdb9cc0..8b1fd74c7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -41,7 +41,8 @@ -- @field #number queueid Unit id of each request in the queue. Essentially a running number starting at one and incremented when a new request is added. -- @field #table stock Table holding all assets in stock. Table entries are of type @{#WAREHOUSE.Assetitem}. -- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. --- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table entries are of type @{#WAREHOUSE.Pendingitem}. +-- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table elements are of type @{#WAREHOUSE.Pendingitem}. +-- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @extends Core.Fsm#FSM --- Manages ground assets of an airbase and offers the possibility to transport them to another airbase or warehouse. @@ -109,6 +110,7 @@ WAREHOUSE = { stock = {}, queue = {}, pending = {}, + defending = {}, } --- Item of the warehouse stock table. @@ -209,7 +211,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.3w" +WAREHOUSE.version="0.2.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -729,8 +731,8 @@ function WAREHOUSE:onafterStatus(From, Event, To) end -- Print queue after processing requests. - self:_PrintQueue(self.queue, "Queue waiting - after request") - self:_PrintQueue(self.pending, "Queue pending - after request") + self:_PrintQueue(self.queue, "Queue waiting - after request") + self:_PrintQueue(self.pending, "Queue pending - after request") end @@ -1429,10 +1431,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - local _parking=Parking[i] -- Spawn with ALIAS here or DCS crashes! - --local _alias=string.format("%s_%d", _assetitem.templatename,_assetitem.id) local _alias=self:_Alias(_assetitem, Request) -- Spawn plane at airport in uncontrolled state. @@ -1666,7 +1666,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! -- Spawn air units. - _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[i]) + _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[_assetitem.uid]) elseif _assetitem.category==Group.Category.TRAIN then @@ -1872,7 +1872,8 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) -- Add a "defender request" to be able to despawn all assets once defeated. if self:IsAttacked() then - self.defenderrequest=request + --self.defenderrequest=request + table.insert(self.defending, request) end -- Remove pending request. @@ -1901,10 +1902,11 @@ end function WAREHOUSE:onafterDefeated(From, Event, To) self:E(self.wid..string.format("Attack was defeated!")) - if self.defenderrequest then + --if self.defenderrequest then + for _,request in pairs(self.defending) do -- Get all assets that were deployed for defending the warehouse. - local request=self.defenderrequest --#WAREHOUSE.Pendingitem + --local request=self.defenderrequest --#WAREHOUSE.Pendingitem -- Route defenders back to warehoue (for visual reasons only) and put them back into stock. for _,_group in pairs(request.cargogroupset:GetSetObjects()) do @@ -1921,9 +1923,13 @@ function WAREHOUSE:onafterDefeated(From, Event, To) end -- Set defender request back to nil. - self.defenderrequest=nil - + --self.defenderrequest=nil + + --self:_DeleteQueueItem(request, self.defending) end + + self.defending=nil + self.defending={} end --- On after "Captured" event. Warehouse has been captured by another coalition. @@ -1942,7 +1948,7 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) self.country=Country self.airbase=nil self.category=-1 - + end --- On after "Destroyed" event. Warehouse was destroyed. Service is stopped. @@ -2974,7 +2980,17 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) end -- TODO Clients? Unoccupied client aircraft are also important! Are they already included in scanned units maybe? - + --[[ + local clients=_DATABASE.CLIENTS + for _,_client in pairs(clients) do + local client=_client --Wrapper.Client#CLIENT + local unit=client:GetClientGroupUnit() + local _coord=unit:GetCoordinate() + local _name=unit:GetName() + local _size=self:_GetObjectSize(client:GetClientGroupDCSUnit()) + table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="client"}) + end + ]] end -- Parking data for all assets. @@ -2994,25 +3010,31 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) for i=1,_asset.nunits do -- Loop over all parking spots. + local gotit=false for _,parkingspot in pairs(parkingdata) do -- Check correct terminal type for asset. We don't want helos in shelters etc. - if AIRBASE._CheckTerminalType(parkingspot.TerminalType,terminaltype) then + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID + local _toac=parkingspot.TOAC -- Loop over all obstacles. local free=true + local problem=nil for _,obstacle in pairs(obstacles[_termid]) do -- Check if aircraft overlaps with any obstacle. - local safe=_overlap(_asset.size, obstacle.size, _spot:Get2DDistance(obstacle.coord)) + local dist=_spot:Get2DDistance(obstacle.coord) + local safe=_overlap(_asset.size, obstacle.size, dist) -- Spot is blocked. if not safe then free=false + problem=obstacle + problem.dist=dist break end @@ -3023,17 +3045,29 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Add parkingspot for this asset unit. table.insert(parking[_asset.uid], parkingspot) + self:E(self.wid..string.format("Parking spot #%d is free for asset id=%d!", _termid, _asset.uid)) + -- Add the unit as obstacle so that this spot will not be available for the next unit. -- TODO Alternatively, I could remove this parking spot from the table, right? - obstacles[_termid]={coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"} + table.insert(obstacles[_termid], {coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"}) + gotit=true + break else - -- Not enough parking available! - return nil + self:E(self.wid..string.format("Parking spot #%d is occupied or not big enough!", _termid)) + local coord=problem.coord --Core.Point#COORDINATE + local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) + coord:MarkToAll(string.format(text)) end end -- check terminal type end -- loop over parking spots + + + if not gotit then + self:E(self.wid..string.format("WARNING: No free parking spot for asset id=%d",_asset.uid)) + return nil + end end -- loop over asset units end -- loop over asset groups @@ -3127,7 +3161,6 @@ function WAREHOUSE:_GetIDsFromGroup(group) return _wid,_aid,_rid end - self:E({_function="getids", group=group}) if group then -- Group name @@ -3137,10 +3170,10 @@ function WAREHOUSE:_GetIDsFromGroup(group) local wid,aid,rid=analyse(name) -- Debug info - self:E(self.wid..string.format("Group Name = %s", tostring(name))) - self:E(self.wid..string.format("Warehouse ID = %s", tostring(wid))) - self:E(self.wid..string.format("Asset ID = %s", tostring(aid))) - self:E(self.wid..string.format("Request ID = %s", tostring(rid))) + self:T3(self.wid..string.format("Group Name = %s", tostring(name))) + self:T3(self.wid..string.format("Warehouse ID = %s", tostring(wid))) + self:T3(self.wid..string.format("Asset ID = %s", tostring(aid))) + self:T3(self.wid..string.format("Request ID = %s", tostring(rid))) return wid,aid,rid else @@ -3395,7 +3428,7 @@ function WAREHOUSE:_PrintQueue(queue, name) if qitem.airbase then airbasename=qitem.airbase:GetName() end - local text=text..string.format("\nUID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%s, Transport=%s, Ntransport=%d.", + text=text..string.format("\nUID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%s, Transport=%s, Ntransport=%d.", qitem.uid, qitem.prio, qitem.warehouse.alias, airbasename, qitem.category, qitem.assetdesc,tostring(qitem.assetdescval), tostring(qitem.nasset), qitem.transporttype, qitem.ntransport) end if #queue==0 then diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 7c9da217c..40282285a 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -4,7 +4,7 @@ -- -- ### Author: **FlightControl** -- --- ### Contributions: +-- ### Contributions: **funkyfranky** -- -- === -- @@ -260,6 +260,16 @@ AIRBASE.PersianGulf = { ["Shiraz_International_Airport"] = "Shiraz International Airport", ["Kerman_Airport"] = "Kerman Airport", } + +--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". +-- @type AIRBASE.ParkingSpot +-- @field Core.Point#COORDINATE Coordinate Coordinate of the parking spot. +-- @field #number TerminalID Terminal ID of the spot. Generally, this is not the same number as displayed in the mission editor. +-- @field #AIRBASE.TerminalType TerminalType Type of the spot, i.e. for which type of aircraft it can be used. +-- @field #boolean TOAC Takeoff or landing aircarft. I.e. this stop is occupied currently by an aircraft until it took of or until it landed. +-- @field #boolean Free This spot is currently free, i.e. there is no alive aircraft on it at the present moment. +-- @field #number TerminalID0 Unknown what this means. If you know, please tell us! +-- @field #number DistToRwy Distance to runway in meters. Currently bugged and giving the same number as the TerminalID. --- Terminal Types of parking spots. See also https://wiki.hoggitworld.com/view/DCS_func_getParking -- @@ -273,7 +283,16 @@ AIRBASE.PersianGulf = { -- * 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 +-- +-- @type AIRBASE.TerminalType +-- @field #number Runway 16: Valid spawn points on runway. +-- @field #number HelicopterOnly 40: Special spots for Helicopers. +-- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map. +-- @field #number OpenMed 72: Open/Shelter air airplane only. +-- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there. +-- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots. +-- @field #number HelicopterUnsable 216: Combines HelicopterOnly, OpenMed and OpenBig. +-- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft. AIRBASE.TerminalType = { Runway=16, HelicopterOnly=40, From c46d6879904ab6eea7e04622f531eb8d54f2f314 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 22 Aug 2018 16:44:58 +0200 Subject: [PATCH 23/73] Warehouse v0.2.4w --- .../Moose/Functional/Warehouse.lua | 350 ++++++++++++------ .../Moose/Wrapper/Controllable.lua | 7 +- 2 files changed, 233 insertions(+), 124 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 8b1fd74c7..e1880c243 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -74,14 +74,86 @@ -- 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. -- +-- # Creating a new warehouse +-- +-- A MOOSE warehouse must be represented in game by a phyical static object. For example, the mission editor already has warehouse as static object available. +-- This would be a good first choice but any static object will do. +-- +-- The positioning of the warehouse static object is very important for a couple of reasons. Firtly, a warehouse needs a good infrastructure so that spawned assets +-- have a proper road connection or can reach the associated airbase easily. +-- +-- Once the static warehouse object is placed in the mission editor it can be used as a MOOSE warehouse by the @{#WAREHOUSE.New}(*warehousestatic*, *alias*) constructor, +-- like for example: +-- +-- warehouse=WAREHOUSE:New(STATIC:FindByName("Warehouse Static Batumi"), "My Warehouse Alias") +-- warehouse:Start() +-- +-- So the first parameter *warehousestatic* is the static MOOSE object. By default, the name of the warehouse will be the same as the name given to the static object. +-- The second parameter *alias* can be used to choose a more convenient name if desired. This will be the name the warehouse calls itself when reporting messages. +-- -- # Adding Assets +-- +-- Assets can be added to the warehouse stock by using the @{#WAREHOUSE.AddAsset}(*group*, *ngroups*) 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. +-- +-- Note that you can also add assets with a delay by using the @{#WAREHOUSE.__AddAsset}(*delay*, *group*, *ngroups*), where *delay* is the delay in seconds before the asset is added. -- --- # Requests +-- # Requesting Assets +-- +-- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinize to check if can be fullfilled at all. If the request is valid, it is +-- put into the warehouse queue and processed as soon as possible. +-- +-- A request can be assed by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*) function. +-- The parameters are +-- +-- * *warehouse*: The requesting MOOSE @{#WAREHOUSE}. Assets will be delivered there. +-- * *AssetDescriptor*: The descriptor to describe the asset "type". See the @{#WAREHOUSE.Descriptor} enumerator. For example, assets requested by their generalized attibute. +-- * *AssetDescriptorValue*: The value of the asset descriptor. +-- * *nAsset*: (Optional) Number of asset group requested. Default is one group. +-- * *TransportType*: (Optional) The transport method used to deliver the assets to the requestor. Default is that assets go to the requesting warehouse on their own. +-- * *nTransport*: (Optional) Number of asset groups used to transport the cargo assets from A to B. Default is one group. +-- * *Prio*: A number between 1 (high) and 100 (low) describing the priority of the request. Request with high priority are processed first. Default is 50, i.e. medium priority. +-- +-- So for example: +-- +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.INFANTRY, 5, WAREHOUSE.TransportType.APC, 2, 20) -- +-- Here, warehouse Kobuleti requests 5 infantry groups from warehouse Batumi. These "cargo" assets should be transported from Batumi to Kobuleti by 2 APCS. +-- Note that the warehouse at Batumi needs to have at least five infantry groups and two APC groups in their stock if the request can be processed. +-- If either to few infantry or APC groups are available when the request is made, the request is held in the warehouse queue until enough cargo and +-- transport assets are available. +-- +-- Also not that the above request is for five infantry units. So any group in stock that has the generalized attribute "INFANTRY" can be selected. +-- +-- A more specific request could look like: +-- +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.UNITTYPE, "A-10C", 2) +-- +-- Here, Kobuleti requests a specific unit type, in particular two groups of A-10Cs. Note that the spelling is important as it must exacly be the same as +-- what one get's when using the DCS unit type. +-- +-- An even more specific request would be: +-- +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Group Name as in ME", 3) +-- +-- In this case three groups named "Group Name as in ME" are requested. So this explicitly request the groups named like that in the Mission Editor. +-- +-- On the other hand, very general unspecifc requests can be made as +-- +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.Ground, 10) +-- +-- Here, Kubuleti requests 10 ground groups and does not care which ones. This could be a mix of infantry, APCs, trucks etc. -- -- === -- --- # USAGE GUIDE +-- # Examples -- -- -- @@ -156,7 +228,7 @@ WAREHOUSE = { --- Descriptors enumerator describing the type of the asset. -- @type WAREHOUSE.Descriptor -- @field #string TEMPLATENAME Name of the asset template. --- @field #string UNITTYPE Typename of the DCS unit. +-- @field #string UNITTYPE Typename of the DCS unit, e.g. "A-10C". -- @field #string ATTRIBUTE Generalized attribute @{#WAREHOUSE.Attribute}. -- @field #string CATEGORY Asset category of type DCS#Group.Category, i.e. GROUND, AIRPLANE, HELICOPTER, SHIP, TRAIN. WAREHOUSE.Descriptor = { @@ -167,10 +239,22 @@ WAREHOUSE.Descriptor = { CATEGORY="category", } ---- Warehouse generalited categories. +--- Generalized asset attributes. Can be used to request assets with certain general characteristics. -- @type WAREHOUSE.Attribute -- @field #string TRANSPORT_PLANE Airplane with transport capability. Usually bigger, i.e. needs larger airbases and parking spots. -- @field #string TRANSPORT_HELO Helicopter with transport capability. +-- @field #string TRANSPORT_APC Amoured Personell Carrier. +-- @field #string FIGHER Fighter, interceptor, ... airplane. +-- @field #string TANKER Airplane which can refuel other aircraft. +-- @field #string AWACS Airborne Early Warning and Control System. +-- @field #string ARTILLERY Artillery assets. +-- @field #string INFANTRY Ground infantry assets. +-- @field #string BOMBER Aircraft which can be used for bombing. +-- @field #string TANK Tanks. +-- @field #string TRUCK Unarmed ground vehicles. +-- @field #string TRAIN Trains. +-- @field #string SHIP Naval assets. +-- @field #string OTHER Anything that does not fall into any other category. WAREHOUSE.Attribute = { TRANSPORT_PLANE="Transport_Plane", TRANSPORT_HELO="Transport_Helo", @@ -189,8 +273,14 @@ WAREHOUSE.Attribute = { OTHER="Other", } ---- Cargo transport type. +--- Cargo transport type. Defines how assets are transported to their destination. -- @type WAREHOUSE.TransportType +-- @field #string AIRPLANE Transports are conducted by airplanes. +-- @field #string HELICOPTER Transports are conducted by helicopters. +-- @field #string APC Transports are conducted by APCs. +-- @field #string SHIP Transports are conducted by ships. +-- @field #string TRAIN Transports are conducted by trains. +-- @field #string SELFPROPELLED Assets go to their destination by themselves. No transport carrier needed. WAREHOUSE.TransportType = { AIRPLANE = "Transport_Plane", HELICOPTER = "Transport_Helo", @@ -211,7 +301,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.4" +WAREHOUSE.version="0.2.4w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -237,6 +327,7 @@ WAREHOUSE.version="0.2.4" -- NOGO: Use RAT for routing air units. Should be possible but might need some modifications of RAT, e.g. explit spawn place. But flight plan should be better. -- TODO: Can I make a request with specific assets? E.g., once delivered, make a request for exactly those assests that were in the original request. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. +-- TODO: Set ROE for spawned groups. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -315,38 +406,38 @@ function WAREHOUSE:New(warehouse, alias) -- Pseudo Functions - --- Triggers the FSM event "Start". Starts the warehouse. + --- Triggers the FSM event "Start". Starts the warehouse. Initializes parameters and starts event handlers. -- @function [parent=#WAREHOUSE] Start -- @param #WAREHOUSE self - --- Triggers the FSM event "Start" after a delay. Starts the warehouse. + --- Triggers the FSM event "Start" after a delay. Starts the warehouse. Initializes parameters and starts event handlers. -- @function [parent=#WAREHOUSE] __Start -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Stop". Stops the warehouse. + --- Triggers the FSM event "Stop". Stops the warehouse and all its event handlers. -- @function [parent=#WAREHOUSE] Stop -- @param #WAREHOUSE self - --- Triggers the FSM event "Stop" after a delay. Stops the warehouse. + --- Triggers the FSM event "Stop" after a delay. Stops the warehouse and all its event handlers. -- @function [parent=#WAREHOUSE] __Stop -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Pause". Pauses the warehouse. + --- Triggers the FSM event "Pause". Pauses the warehouse. Assets can still be added and requests be made. However, requests are not processed. -- @function [parent=#WAREHOUSE] Pauses -- @param #WAREHOUSE self - --- Triggers the FSM event "Pause" after a delay. Pause the warehouse. + --- Triggers the FSM event "Pause" after a delay. Pauses the warehouse. Assets can still be added and requests be made. However, requests are not processed. -- @function [parent=#WAREHOUSE] __Pause -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. - --- Triggers the FSM event "Unpause". Pauses the warehouse. + --- Triggers the FSM event "Unpause". Unpauses the warehouse. Processing of queued requests is resumed. -- @function [parent=#WAREHOUSE] UnPause -- @param #WAREHOUSE self - --- Triggers the FSM event "Unpause" after a delay. Pause the warehouse. + --- Triggers the FSM event "Unpause" after a delay. Unpauses the warehouse. Processing of queued requests is resumed. -- @function [parent=#WAREHOUSE] __Unpause -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. @@ -428,13 +519,83 @@ function WAREHOUSE:New(warehouse, alias) --- 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 Core.Set#SET_GROUP groupset Set of groups that were delivered. + -- @param #WAREHOUSE.Pendingitem request Pending request that was now 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 Core.Set#SET_GROUP groupset Set of groups that were delivered. + -- @param #WAREHOUSE.Pendingitem request Pending request that was now delivered. + + + --- Triggers the FSM event "SelfRequest". Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. + -- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, + -- this request is used to put the groups back into the warehouse stock. + -- @function [parent=#WAREHOUSE] SelfRequest + -- @param #WAREHOUSE self + -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. + -- @param #WAREHOUSE.Pendingitem request Pending self request. + + --- Triggers the FSM event "SelfRequest" with a delay. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. + -- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, + -- this request is used to put the groups back into the warehouse stock. + -- @function [parent=#WAREHOUSE] __SelfRequest + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. + -- @param #WAREHOUSE.Pendingitem request Pending self request. + + + --- Triggers the FSM event "Attacked" when a warehouse is under attack by an another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] Attacked + -- @param DCS#coalition.side Coalition which is attacking the warehouse. + -- @param DCS#country.id Country which is attacking the warehouse. + + --- Triggers the FSM event "Attacked" with a delay when a warehouse is under attack by an another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] __Attacked + -- @param #number delay Delay in seconds. + -- @param DCS#coalition.side Coalition which is attacking the warehouse. + -- @param DCS#country.id Country which is attacking the warehouse. + + + --- Triggers the FSM event "Defeated" when an attack from an enemy was defeated. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] Defeated + -- @param DCS#coalition.side Coalition which is attacking the warehouse. + -- @param DCS#country.id Country which is attacking the warehouse. + + --- Triggers the FSM event "Defeated" with a delay when an attack from an enemy was defeated. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] __Defeated + -- @param #number delay Delay in seconds. + -- @param DCS#coalition.side Coalition which is attacking the warehouse. + -- @param DCS#country.id Country which is attacking the warehouse. + + + --- Triggers the FSM event "Captured" when a warehouse has been captured by another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] Captured + -- @param DCS#coalition.side Coalition which captured the warehouse. + -- @param DCS#country.id Country which has captured the warehouse. + + --- Triggers the FSM event "Captured" with a delay when a warehouse has been captured by another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] __Captured + -- @param #number delay Delay in seconds. + -- @param DCS#coalition.side Coalition which captured the warehouse. + -- @param DCS#country.id Country which has captured the warehouse. + + + --- Triggers the FSM event "Destroyed" when the warehouse was destroyed. All services are stopped. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] Destroyed + + --- Triggers the FSM event "Destroyed" with a delay when the warehouse was destroyed. All services are stopped. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] Destroyed + -- @param #number delay Delay in seconds. return self end @@ -1327,24 +1488,32 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local group=_spawngroup --Wrapper.Group#GROUP - local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate(50) - ToCoordinate:MarkToAll("Destination") -- Route cargo to their destination. - if _cargocategory==Group.Category.GROUND then + if _cargocategory==Group.Category.GROUND then env.info("FF route ground "..group:GetName()) - self:_RouteGround(group, ToCoordinate) - elseif _cargocategory==Group.Category.AIRPLANE then - env.info("FF route plane "..group:GetName()) - self:_RouteAir(group, Request.airbase) - elseif _cargocategory==Group.Category.HELICOPTER then - env.info("FF route helo "..group:GetName()) + + -- Random place in the spawn zone of the requesting warehouse. + local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate() + ToCoordinate:MarkToAll("Destination") + + -- Route ground. + self:_RouteGround(group, Request) + + elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + env.info("FF route aircraft "..group:GetName()) + + -- Route plane the the requesting warehouses airbase. + -- Actually, the route is already set. We only need to activate the uncontrolled group. self:_RouteAir(group, Request.airbase) + elseif _cargocategory==Group.Category.SHIP then self:E("ERROR: self propelled ship not implemented yet!") elseif _cargocategory==Group.Category.TRAIN then env.info("FF route train "..group:GetName()) - self:_RouteTrain(group, ToCoordinate) + + -- Route train to the rail connection of the requesting warehouse. + self:_RouteTrain(group, Request.warehouse.rail) else self:E(self.wid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory), tostring(group:GetName()))) end @@ -1627,8 +1796,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_FindParkingForAssets(self.airbase,_assetstock) - --Parking=self:_GetParkingForAssets(_assetstock) + Parking=self:_FindParkingForAssets(self.airbase,_assetstock) end -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. @@ -1828,12 +1996,12 @@ function WAREHOUSE:_UpdatePending(group) end ---- On after "Delivered" event. +--- On after "Delivered" event. Triggered when all asset groups have reached their destination. Corresponding request is deleted from the pending queue. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #WAREHOUSE.Pendingitem request +-- @param #WAREHOUSE.Pendingitem request The pending request that is finished and deleted from the pending queue. function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Debug info @@ -1851,11 +2019,13 @@ function WAREHOUSE:onafterDelivered(From, Event, To, request) end --- On after "SelfRequest" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. +-- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, +-- this request is used to put the groups back into the warehouse stock. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered. +-- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. -- @param #WAREHOUSE.Pendingitem request Pending self request. function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) @@ -1894,7 +2064,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) end ---- On after "Defeated" event. Warehouse defeated an attack by another coalition. +--- On after "Defeated" event. Warehouse defeated an attack by another coalition. Defender assets are added back to warehouse stock. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -1951,7 +2121,7 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) end ---- On after "Destroyed" event. Warehouse was destroyed. Service is stopped. +--- On after "Destroyed" event. Warehouse was destroyed. All services are stopped. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -1968,30 +2138,41 @@ end --- 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 Wrapper.Group#GROUP group The ground group to be routed +-- @param #WAREHOUSE.Queueitem request The request for this group. -- @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) +function WAREHOUSE:_RouteGround(group, request) - if Group and Group:IsAlive() then + if group and group:IsAlive() then - -- Set speed. - local _speed=Speed or Group:GetSpeedMax()*0.6 + -- Set speed to 70% of max possible. + local _speed=group:GetSpeedMax()*0.7 -- Create task. - -- TODO: It might be necessary to ALWAYS route the group to the road connection first. - -- At the moment, the random spawn point might give another first road point which could also be a dead end like in Kobuliti(?). - local Waypoints, canroad = Group:TaskGroundOnRoad(Coordinate, _speed, "Off Road", true) + -- DONE: It might be necessary to ALWAYS route the group to the road connection first. + -- At the moment, the random spawn point might give another first road point which could also be a dead end like in Kobuliti(?). + -- TODO: Might be necessary to include the current coordinate as first waypoint?! + + -- Waypoints for road-to-road connection. + local Waypoints, canroad = group:TaskGroundOnRoad(request.warehouse.road, _speed, "Off Road", false, self.road) + + -- First waypoint = current position of the group. + local FromWP=group:GetCoordinate():WaypointGround(_speed, "Off Road") + table.insert(Waypoints, FromWP, 1) + + -- Final coordinate. + local ToWP=request.warehouse.spawnzone:GetRandomCoordinate():WaypointGround(_speed, "Off Road") + table.insert(Waypoints, ToWP, #Waypoints) -- Task function triggering the arrived event. - local TaskFunction = Group:TaskFunction("WAREHOUSE._Arrived", self) + local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] - Group:SetTaskWaypoint(Waypoint, TaskFunction) + group:SetTaskWaypoint(Waypoint, TaskFunction) -- Route group to destination. - Group:Route(Waypoints, 1) + group:Route(Waypoints, 1) end end @@ -2839,74 +3020,6 @@ function WAREHOUSE:_GetTerminal(_attribute) return _terminal end ---- Get parking data for all air assets that need to be spawned at an airbase. ---@param #WAREHOUSE self ---@param #table assetlist A list of assets for which parking spots are required. ---@param #table parkingdata Table of the complete parking data to check. Default is to take it from the @{Wrapper.Airbase#AIRBASE.GetParkingSpotsTable}() function. ---@return #table A table with parking spots for each asset group. ---@return #table The reduced parking data table of the spots that have not been assigned. -function WAREHOUSE:_GetParkingForAssets(assetlist, parkingdata) - - --- Remove selected spots from parking data table. - local function removeparking(parkingdata,spots) - for j=1,#spots do - for i=1,#parkingdata do - if parkingdata[i].TerminalID==spots[j].TerminalID then - table.remove(parkingdata, i) - break - end - end - end - end - - -- Get complete parking data of the airbase. - parkingdata=parkingdata or self.airbase:GetParkingSpotsTable() - - local assetparking={} - for i=1,#assetlist do - - -- Asset specifics. - local asset=assetlist[i] --#WAREHOUSE.Assetitem - local group=GROUP:FindByName(asset.templatename) - local nunits=#group:GetUnits() - local terminal=self:_GetTerminal(asset.attribute) - - -- Debug info - env.info(string.format("Parking spot search:")) - env.info(string.format("Asset name = %s", asset.templatename)) - env.info(string.format("Asset attribute = %s", asset.attribute)) - env.info(string.format("Terminal type = %d", terminal)) - env.info(string.format("Unit number = %d", nunits)) - env.info(string.format("Parking spots = %d", #parkingdata)) - - -- Find appropiate parking spots for this group. - local spots=self.airbase:FindFreeParkingSpotForAircraft(group, terminal, nil, nil, nil, nil, nil, nil, parkingdata) - - for _,spot in pairs(spots) do - if spot then - local coord=spot.Coordinate --Core.Point#COORDINATE - local text=string.format("Parking spot for %s:\nAsset id=%d, Terminal id=%d", asset.templatename, asset.uid, spot.TerminalID) - --coord:MarkToAll(text) - self:E(self.wid..text) - end - end - - -- Not enough parking spots for this group. - if #spots Date: Thu, 23 Aug 2018 00:31:40 +0200 Subject: [PATCH 24/73] Warehouse v0.2.5 --- .../Moose/Functional/Warehouse.lua | 188 +++++++++++++++--- 1 file changed, 155 insertions(+), 33 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e1880c243..0c6e24136 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -301,7 +301,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.4w" +WAREHOUSE.version="0.2.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -382,26 +382,28 @@ function WAREHOUSE:New(warehouse, alias) self:SetStartState("Stopped") -- Add FSM transitions. - self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. - self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. - self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. - self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. - self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. - self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. - self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. - self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. - self:AddTransition("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("*", "Stop", "Stopped") -- TODO Stop the warehouse. - self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. - self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. - self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! - self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. - self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. + self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. + self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. + self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. + self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. + self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. + self:AddTransition("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("*", "Stop", "Stopped") -- TODO Stop the warehouse. + self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. + self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. + self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! + self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. + self:AddTransition("*", "AirbaseCaptured", "*") -- TODO Airbase was captured by other coalition. + self:AddTransition("*", "AirbaseRecaptured", "*") -- TODO Airbase was re-captured from other coalition. + self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. -- Pseudo Functions @@ -588,6 +590,30 @@ function WAREHOUSE:New(warehouse, alias) -- @param DCS#country.id Country which has captured the warehouse. + --- Triggers the FSM event "AirbaseCaptured" when the airbase of the warehouse has been captured by another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] AirbaseCaptured + -- @param DCS#coalition.side Coalition which captured the airbase. + + --- Triggers the FSM event "AirbaseCaptured" with a delay when the airbase of the warehouse has been captured by another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] __AirbaseCaptured + -- @param #number delay Delay in seconds. + -- @param DCS#coalition.side Coalition which captured the airbase. + + + --- Triggers the FSM event "AirbaseRecaptured" when the airbase of the warehouse has been re-captured from the other coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] AirbaseRecaptured + -- @param DCS#coalition.side Coalition which re-captured the airbase. + + --- Triggers the FSM event "AirbaseRecaptured" with a delay when the airbase of the warehouse has been re-captured from the other coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] __AirbaseRecaptured + -- @param #number delay Delay in seconds. + -- @param DCS#coalition.side Coalition which re-captured the airbase. + + --- Triggers the FSM event "Destroyed" when the warehouse was destroyed. All services are stopped. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] Destroyed @@ -864,8 +890,7 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterStatus(From, Event, To) - self:E(self.wid..string.format("Checking status of warehouse %s. Current FSM state %s. Global warehouse asssets = %d.", self.alias, self:GetState(), #WAREHOUSE.db.Assets)) - --env.info(string.format("FF number of global assets = %d, current asset id = %d", #WAREHOUSE.db.Assets, WAREHOUSE.db.AssetID)) + self:E(self.wid..string.format("Checking status of warehouse %s. Current FSM state %s. Global warehouse assets = %d.", self.alias, self:GetState(), #WAREHOUSE.db.Assets)) -- Print status. self:_DisplayStatus() @@ -1390,6 +1415,13 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Filter the requested assets. local _assets,_nasset,_enough=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + if Request.nasset==0 then + local text=string.format("Request denied! Zero assets were requested.") + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + return false + end + -- Check if destination is in range for all requested assets. for _,_asset in pairs(_assets) do local asset=_asset --#WAREHOUSE.Assetitem @@ -1780,10 +1812,13 @@ end -- @return Core.Set#SET_GROUP Set of groups that were spawned. -- @return #table List of spawned assets. function WAREHOUSE:_SpawnAssetRequest(Request) + self:E({requestUID=Request.uid}) -- Filter the requested cargo assets. local _assetstock,_nasset,_enough=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + self:E({num_assetstoc=#_assetstock, nasset=_nasset, enough=_enough}) + -- No assets in stock :( if not _enough then return nil,nil,nil @@ -2007,17 +2042,22 @@ function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Debug info self:E(self.wid..string.format("All assets from warehouse %s delivered to warehouse %s!", self.alias, request.warehouse.alias)) + self:_Fireworks(request.warehouse.coordinate) + + --[[ -- Fireworks! for i=1,91 do local color=math.random(0,3) request.warehouse.coordinate:Flare(color, i-1) end + ]] -- Remove pending request: self:_DeleteQueueItem(request, self.pending) end + --- On after "SelfRequest" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. -- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, -- this request is used to put the groups back into the warehouse stock. @@ -2114,13 +2154,62 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) -- Respawn warehouse with new coalition/country. self.warehouse:ReSpawn(Country) + + -- Set new country and coalition self.coalition=Coalition self.country=Country - self.airbase=nil - self.category=-1 + + -- Delete all waiting requests because they are not valid any more + self.queue=nil + self.queue={} + + --TODO: What about pending items? Is there any problem due to the coalition change? + --TODO: Maybe if the receiving warehouse gets captured! Oh, oh :( + -- What to do? send the items back? Impossible. + + -- Airbase could have been captured before and already belongs to the new coalition. + local airbase=AIRBASE:FindByName(self.airbasename) + local airbasecoaltion=airbase:GetCoalition() + + if self.coalition==airbasecoaltion then + -- Airbase already owned by the coalition that captured the warehouse. Airbase can be used by this warehouse. + self.airbase=airbase + self.category=airbase:GetDesc().category + else + -- Airbase is owned by other coalition. So this warehouse does not have an airbase unil it is captured. + self.airbase=nil + self.category=-1 + end end +--- On after "AirbaseCaptured" event. Airbase of warehouse has been captured by another coalition. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param DCS#coalition.side Coalition which captured the warehouse. +function WAREHOUSE:onafterAirbaseCaptured(From, Event, To, Coalition) + self:E(self.wid..string.format("Our airbase %s was captured by coalition %d!", self.airbasename, Coalition)) + self.airbase:GetCoordinate():SmokeRed() + self.airbase=nil + self.category=-1 -- -1 indicates no airbase. +end + +--- On after "AirbaseRecaptured" event. Airbase of warehouse has been re-captured from other coalition. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param DCS#coalition.side Coalition which captured the warehouse. +function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) + self:E(self.wid..string.format("We re-capturd our airbase %s from coalition %d!", self.airbasename, Coalition)) + self.airbase=AIRBASE:FindByName(self.airbasename) + self.category=self.airbase:GetDesc().category + self.airbase:GetCoordinate():SmokeGreen() +end + + --- On after "Destroyed" event. Warehouse was destroyed. All services are stopped. -- @param #WAREHOUSE self -- @param #string From From state. @@ -2158,11 +2247,11 @@ function WAREHOUSE:_RouteGround(group, request) -- First waypoint = current position of the group. local FromWP=group:GetCoordinate():WaypointGround(_speed, "Off Road") - table.insert(Waypoints, FromWP, 1) + table.insert(Waypoints, 1, FromWP) -- Final coordinate. local ToWP=request.warehouse.spawnzone:GetRandomCoordinate():WaypointGround(_speed, "Off Road") - table.insert(Waypoints, ToWP, #Waypoints) + table.insert(Waypoints, #Waypoints+1, ToWP) -- Task function triggering the arrived event. local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) @@ -2441,13 +2530,15 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBaseCaptured(EventData) - self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.alias)) -- This warehouse does not have an airbase and never had one. So i could not be captured. if self.airbasename==nil then + -- This warehouse never had an airbase so I cannot have been captured. return end + self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.alias)) + if EventData and EventData.Place then -- Place is the airbase that was captured. @@ -2456,6 +2547,8 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) if EventData.PlaceName==self.airbasename then -- Okay, this airbase belongs or did belong to this warehouse. + self:E(self.wid..string.format("Airbase of warehouse %s was captured! ",self.alias)) + -- New coalition of airbase after it was captured. local coalitionAirbase=airbase:GetCoalition() @@ -2466,13 +2559,14 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) -- Warehouse lost this airbase previously and not it was re-captured. env.info("FF airbase of warehouse is nil") if coalitionAirbase == self.coalition then - self.airbase=airbase - env.info("FF air") + self:AirbaseRecaptured(coalitionAirbase) + --self.airbase=airbase end else -- Captured airbase belongs to this warehouse but was captured by other coaltion. if coalitionAirbase ~= self.coalition then - self.airbase=nil + self:AirbaseCaptured(coalitionAirbase) + --self.airbase=nil end end @@ -2628,6 +2722,7 @@ end -- @param #table queue The queue which is holding the requests to check. -- @return #boolean If true, request can be executed. If false, something is not right. function WAREHOUSE:_CheckRequestConsistancy(queue) + env.info("FF checking request consistancy!") -- Requests to delete. local invalid={} @@ -2642,6 +2737,12 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- 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... + + -- Check if at least one asset was requested. + if request.nasset==0 then + self:E(self.wid..string.format("ERROR: Incorrect request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock.")) + valid=false + end -- Request from enemy coalition? if self.coalition~=request.warehouse.coalition then @@ -2858,7 +2959,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) end -- Add request as unvalid and delete it later. - if not valid then + if not valid then table.insert(invalid, request) end @@ -2867,8 +2968,9 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Delete invalid requests. for _,_request in pairs(invalid) do + self:E(self.wid..string.format("Deleting invalid request %d",_request.uid)) self:_DeleteQueueItem(_request, self.queue) - end + end end @@ -3316,6 +3418,11 @@ function WAREHOUSE:_FilterStock(stock, item, value, nmax) end end + -- Treat case where ntot=0, i.e. no assets at all. + if ntot==0 then + return filtered, ntot, false + end + -- Handle string input for nmax. if type(nmax)=="string" then if nmax:lower()=="all" then @@ -3631,6 +3738,21 @@ function WAREHOUSE:_DisplayStockItems(stock) MESSAGE:New(text, 10):ToAll() end +--- Fireworks! +-- @param #WAREHOUSE self +-- @param Core.Point#COORDINATE coord +function WAREHOUSE:_Fireworks(coord) + + -- Place. + coord=coord or self.coordinate + + -- Fireworks! + for i=1,91 do + local color=math.random(0,3) + coord:Flare(color, i-1) + end +end + --- Make a flight plan from a departure to a destination airport. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Assetitem asset From 7148fe0c128bc9c49ff075a423de390c6149a0de Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 23 Aug 2018 16:30:38 +0200 Subject: [PATCH 25/73] Warehouse v0.25w --- .../Moose/Functional/Warehouse.lua | 117 ++++++------------ 1 file changed, 37 insertions(+), 80 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0c6e24136..d13aaf6e5 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -301,33 +301,35 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.5" +WAREHOUSE.version="0.2.5w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Add event handlers. +-- TODO: Set ROE for spawned groups. +-- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. +-- DONE: If warehouse is destoyed, all asssets are gone. +-- TODO: Write documentation. +-- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. +-- TODO: Handle cases with immobile units. +-- TODO: Handle cargo crates. +-- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? +-- TODO: Add general message function for sending to coaliton or debug. +-- TODO: Fine tune event handlers. +-- DONE: Add event handlers. -- DONE: Add AI_CARGO_AIRPLANE -- DONE: Add AI_CARGO_APC -- DONE: Add AI_CARGO_HELICOPTER -- DONE: Switch to AI_CARGO_XXX_DISPATCHER -- DONE: Add queue. --- TODO: Write documentation. -- DONE: Put active groups into the warehouse, e.g. when they were transported to this warehouse. -- NOGO: Spawn warehouse assets as uncontrolled or AI off and activate them when requested. --- TODO: Handle cases with immobile units. -- DONE: How to handle multiple units in a transport group? <== Cargo dispatchers. -- DONE: Add phyical object. --- TODO: If warehouse is destoyed, all asssets are gone. --- TODO: If warehosue is captured, change warehouse and assets to other coalition. --- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? --- TODO: Handle cargo crates. --- TODO: Add general message function for sending to coaliton or debug. +-- DONE: If warehosue is captured, change warehouse and assets to other coalition. -- NOGO: Use RAT for routing air units. Should be possible but might need some modifications of RAT, e.g. explit spawn place. But flight plan should be better. --- TODO: Can I make a request with specific assets? E.g., once delivered, make a request for exactly those assests that were in the original request. --- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. --- TODO: Set ROE for spawned groups. +-- DONE: Can I make a request with specific assets? E.g., once delivered, make a request for exactly those assests that were in the original request. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor(s) @@ -2262,6 +2264,10 @@ function WAREHOUSE:_RouteGround(group, request) -- Route group to destination. group:Route(Waypoints, 1) + + -- Set ROE and alaram state. + group:OptionROEReturnFire() + group:OptionAlarmStateGreen() end end @@ -2269,73 +2275,18 @@ end --- Route the airplane from one airbase another. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP Aircraft 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) +function WAREHOUSE:_RouteAir(aircraft) - if Aircraft and Aircraft:IsAlive()~=nil then + if aircraft and aircraft:IsAlive()~=nil then -- Start command. - if true then - local StartCommand = {id = 'Start', params = {}} - Aircraft:SetCommand(StartCommand) - return - end + local StartCommand = {id = 'Start', params = {}} + aircraft:SetCommand(StartCommand) - -- Set takeoff type. - local Takeoff = SPAWN.Takeoff.Cold - - -- Get template of group. - local Template = Aircraft:GetTemplate() - - -- Nil check - if Template==nil then - self:E(self.wid.."ERROR: Template nil in RouteAir!") - return - end - - local Waypoints,Coordinates=self:_GetFlightplan(Aircraft,self.airbase,ToAirbase) - - --[[ - -- 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 + -- Set ROE and alaram state. + aircraft:OptionROEReturnFire() + aircraft:OptionROTPassiveDefense() - -- Second point of the route. First point is done in RespawnAtCurrentAirbase() routine. - Template.route.points[2] = ToWaypoint - ]] - - -- Set waypoints. - Template.route.points=Waypoints - - -- Respawn group at the current airbase. - env.info("FF Respawn at current airbase group = "..Aircraft:GetName().." name before") - local newAC=Aircraft:RespawnAtCurrentAirbase(Template, Takeoff, false) - env.info("FF Respawn at current airbase group = "..newAC:GetName().." name after") else self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(Aircraft:GetName()), tostring(Aircraft:IsAlive()))) @@ -2432,6 +2383,7 @@ function WAREHOUSE:_OnEventArrived(EventData) local nunits=#group:GetUnits() local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. self:__Arrived(dt, group) + else self:E(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.", tostring(wid), tostring(aid), tostring(rid))) end @@ -2504,7 +2456,15 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventEngineShutdown(EventData) - self:E(self.wid..string.format("Warehouse %s captured event engine shutdown!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event engine shutdown!", self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:E(self.wid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end end --- Warehouse event handling function. @@ -2547,7 +2507,7 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) if EventData.PlaceName==self.airbasename then -- Okay, this airbase belongs or did belong to this warehouse. - self:E(self.wid..string.format("Airbase of warehouse %s was captured! ",self.alias)) + self:I(self.wid..string.format("Airbase of warehouse %s was captured! ",self.alias)) -- New coalition of airbase after it was captured. local coalitionAirbase=airbase:GetCoalition() @@ -2557,16 +2517,13 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) -- Warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place if self.airbase==nil then -- Warehouse lost this airbase previously and not it was re-captured. - env.info("FF airbase of warehouse is nil") if coalitionAirbase == self.coalition then self:AirbaseRecaptured(coalitionAirbase) - --self.airbase=airbase end else -- Captured airbase belongs to this warehouse but was captured by other coaltion. if coalitionAirbase ~= self.coalition then self:AirbaseCaptured(coalitionAirbase) - --self.airbase=nil end end @@ -2607,7 +2564,7 @@ function WAREHOUSE:_CheckConquered() local distance=coord:Get2DDistance(unit:GetCoordinate()) -- Filter only alive groud units. Also check distance again, because the scan routine might give some larger distances. - if unit:IsGround() and unit:IsAlive() and distance<= radius then + if unit:IsGround() and unit:IsAlive() and distance <= radius then -- Get coalition and country. local _coalition=unit:GetCoalition() From 743b59546582c87fc9218fcac24cd806f2bb7972 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 24 Aug 2018 11:09:51 +0200 Subject: [PATCH 26/73] Warehouse v0.2.6w --- .../Moose/Functional/Warehouse.lua | 81 ++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index d13aaf6e5..0db133ebd 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -132,6 +132,8 @@ -- -- Also not that the above request is for five infantry units. So any group in stock that has the generalized attribute "INFANTRY" can be selected. -- +-- ### Requesting a Specific Unit Type +-- -- A more specific request could look like: -- -- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.UNITTYPE, "A-10C", 2) @@ -139,17 +141,85 @@ -- Here, Kobuleti requests a specific unit type, in particular two groups of A-10Cs. Note that the spelling is important as it must exacly be the same as -- what one get's when using the DCS unit type. -- +-- ### Requesting a Specifc Group +-- -- An even more specific request would be: -- -- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Group Name as in ME", 3) -- -- In this case three groups named "Group Name as in ME" are requested. So this explicitly request the groups named like that in the Mission Editor. -- +-- ### Requesting a general category +-- -- On the other hand, very general unspecifc requests can be made as -- -- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.Ground, 10) -- -- Here, Kubuleti requests 10 ground groups and does not care which ones. This could be a mix of infantry, APCs, trucks etc. +-- +-- # Employing Assets +-- +-- Assets in the warehouse' stock can used for user defined tasks realtively easily. They can be spawned into the game by a "self request", i.e. the warehouse +-- requests the assets from itself: +-- +-- warehouseBatumi:AddRequest(warehouseBatumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.INFANTRY, 5) +-- +-- This would simply spawn five infantry groups in the spawn zone of the Batumi warehouse if/when they are available. +-- +-- ## Accessing the Assets +-- +-- If a warehouse requests assets from itself, it triggers the event **SelfReqeuest**. The mission designer can capture this event with the associated +-- @{#WAREHOUSE.OnAfterSelfRequest}(*From*, *Event*, *To*, *groupset*, *request*) function. +-- +-- --- OnAfterSelfRequest user function. Access groups spawned from the warehouse for further tasking. +-- -- @param #WAREHOUSE self +-- -- @param #string From From state. +-- -- @param #string Event Event. +-- -- @param #string To To state. +-- -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. +-- -- @param #WAREHOUSE.Pendingitem request Pending self request. +-- function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) +-- +-- for _,_group in pairs(groupset:GetSetObjects()) do +-- local group=_group --Wrapper.Group#GROUP +-- group:SmokeGreen() +-- end +-- +-- 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 +-- for further tasking. Here, the groups are only smoked but, of course, you can use them for whatever task you imagine. +-- +-- Note that airborn groups are spawned in uncontrolled state and need to be activated first before they can start their assigned mission. +-- +-- # Strategic Tasks +-- +-- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a jucy task for enemy attacks. +-- There are several interesting situations, which can occurr. +-- +-- ## Capturing a Warehouse' Airbase +-- +-- If a warehouse has an associated airbase, it can be captured by the enemy. In this case, the warehouse looses it ability so employ all airborn assets and is also cut-off +-- from supply by airborn units. +-- +-- Technically, the capturing of the airbase is triggered by the DCS S_EVENT_CAPTURE_BASE event. So the capturing takes place when only enemy ground units are in the +-- airbase zone whilst no ground units of the present airbase owner are in that zone. +-- +-- The warehouse will also create an event named "AirbaseCaptured", which can be captured by the @{#WAREHOUSE.OnAfterAirbaseCaptured} function. So the warehouse can react on +-- this attack and for example spawn ground groups to re-capture its airbase. +-- +-- When an airbase is re-captured the event "AirbaseRecaptured" is triggered and can be captured by the @{#WAREHOUSE.OnAfterAirbaseRecaptured} function. +-- This can be used to put the defending assets back into the warehouse stock. +-- +-- ## Capturing the Warehouse +-- +-- A warehouse can also be captured by the enemy coaltion. If enemy groups enter the warehouse zone the event "Attacked" is triggered which can be captured by the +-- @{#WAREHOUSE.OnAfterAttacked} event. +-- +-- If a warehouse is attacked it will spawn all its ground assets in the spawn zone which can than be used to defend the warehouse zone. +-- +-- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy. +-- This event triggered in this case is -- -- === -- @@ -301,7 +371,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.5w" +WAREHOUSE.version="0.2.6w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -549,6 +619,15 @@ function WAREHOUSE:New(warehouse, alias) -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. -- @param #WAREHOUSE.Pendingitem request Pending self request. + --- On after "SelfRequest" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. + -- @function [parent=#WAREHOUSE] OnAfterSelfRequest + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Core.Set#SET_GROUP groupset The set of (cargo) groups that was delivered to the warehouse itself. + -- @param #WAREHOUSE.Pendingitem request Pending self request. + --- Triggers the FSM event "Attacked" when a warehouse is under attack by an another coalition. -- @param #WAREHOUSE self From 76f34e448c820a816382a33ff83033b7ebff8ac7 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 24 Aug 2018 11:59:59 +0200 Subject: [PATCH 27/73] Warehouse v0.2.6w --- .../Moose/Functional/Warehouse.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0db133ebd..2bcf70e56 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -194,7 +194,7 @@ -- -- # Strategic Tasks -- --- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a jucy task for enemy attacks. +-- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a juicy task for enemy attacks. -- There are several interesting situations, which can occurr. -- -- ## Capturing a Warehouse' Airbase @@ -213,13 +213,24 @@ -- -- ## Capturing the Warehouse -- --- A warehouse can also be captured by the enemy coaltion. If enemy groups enter the warehouse zone the event "Attacked" is triggered which can be captured by the +-- A warehouse can also be captured by the enemy coaltion. If enemy groups enter the warehouse zone the event **Attacked** is triggered which can be captured by the -- @{#WAREHOUSE.OnAfterAttacked} event. -- -- If a warehouse is attacked it will spawn all its ground assets in the spawn zone which can than be used to defend the warehouse zone. -- -- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy. --- This event triggered in this case is +-- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function. +-- +-- The warehouse turn to the capturing coalition, i.e. its physical representation, and all assets as well. In paticular, all requests to the warehouse will +-- spawn assets beloning to the new owner. +-- +-- ## Destroying a Warehouse +-- +-- If an enemy destroy the physical warehouse structure, the warehouse will of course stop all its services. In priciple, all assets contained in the warehouse are +-- gone as well. So a warehouse should be properly defended. +-- +-- Upon destruction of the warehouse, the event **Destroyed** is triggered, which can be captured by the @{#WAREHOUSE.OnAfterDestroyed} function. +-- So the mission designer can invene at this point and for example choose to spawn all or paricular types of assets before the warehouse is gone for good. -- -- === -- From 5b7852ef6cfbc48b69018bed2bac601c31859310 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 24 Aug 2018 21:19:53 +0200 Subject: [PATCH 28/73] Warehouse v0.2.5 --- Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua | 2 ++ Moose Development/Moose/AI/AI_Cargo_Helicopter.lua | 6 +++++- Moose Development/Moose/Functional/Warehouse.lua | 11 ++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 75ae757bb..5bda17ffe 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -368,6 +368,8 @@ end -- @param #AI_CARGO_DISPATCHER self function AI_CARGO_DISPATCHER:onafterMonitor() + env.info("FF number of cargo set = "..self.SetCargo:Count()) + for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do local Carrier = Carrier -- Wrapper.Group#GROUP local AI_Cargo = self.AI_Cargo[Carrier] diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index dec71fd3e..540d4b064 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -642,7 +642,11 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin if Helicopter and Helicopter:IsAlive() ~= nil then - Helicopter:Activate() + --Helicopter:Activate() + + env.info("FF route pickup") + + Coordinate:MarkToAll("helo pickupcoord") self.RoutePickup = true Coordinate.y = math.random( 50, 500 ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index d13aaf6e5..9dd2a6be9 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1628,6 +1628,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Dependent on transport type, spawn the transports and set up the dispatchers. if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + ---------------- + --- AIRPLANE --- + ---------------- -- Spawn the transport groups. for i=1,Request.ntransport do @@ -1662,6 +1665,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + ------------------ + --- HELICOPTER --- + ------------------ -- Spawn the transport groups. for i=1,Request.ntransport do @@ -1702,6 +1708,9 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --CargoTransport:SetHomeZone(self.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.APC then + ----------- + --- APC --- + ----------- -- Spawn the transport groups. for i=1,Request.ntransport do @@ -1733,7 +1742,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_APC:NewWithZones(TransportSet, CargoGroups, DeployZoneSet, 0) + CargoTransport = AI_CARGO_DISPATCHER_APC:New(TransportSet, CargoGroups, DeployZoneSet, 0) -- Set home zone. CargoTransport:SetHomeZone(self.spawnzone) From 64355fb772de98cc2e36a97e8ed0360f834c37a8 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 26 Aug 2018 17:29:31 +0200 Subject: [PATCH 29/73] Warehouse v0.2.6 Several fixes including BoundZone. Warehouse ID is not number not string. Updated Documentation. Fixed bug that invalid requests are not recognized. --- .../Moose/Functional/Warehouse.lua | 499 +++++++++++------- Moose Development/Moose/Wrapper/Group.lua | 3 +- Moose Development/Moose/Wrapper/Object.lua | 3 +- 3 files changed, 310 insertions(+), 195 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 4f985cf6f..0be2f17a5 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1,4 +1,4 @@ ---- **Functional** - (R2.4) - Manages assets of an airbase and transportation to other airbases upon request. +--- **Functional** - (R2.5) - Simulation of logistics. -- -- -- Features: @@ -9,6 +9,8 @@ -- * Different means of automatic transportation (planes, helicopters, APCs, selfpropelled). -- -- # QUICK START GUIDE +-- +-- **WIP** -- -- === -- @@ -23,7 +25,7 @@ -- @field #boolean Debug If true, send debug messages to all. -- @field #boolean Report If true, send status messages to coalition. -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. --- @field DCS#coalition.side coalition Coalition ID the warehouse belongs to. +-- @field DCS#coalition.side coalition Coalition side the warehouse belongs to. -- @field DCS#country.id country Country ID the warehouse belongs to. -- @field #string alias Alias of the warehouse. Name its called when sending messages. -- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coaliton. @@ -63,20 +65,50 @@ -- -- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.JPG) -- --- # What is a warehouse? +-- # The Warehouse Concept +-- +-- The MOOSE warehouse adds a new logistic component to the DCS World. *Assets*, i.e. ground, airborne and naval units, can be transferred from one place +-- to another in a realistic and highly automatic fashion. In contrast to a "DCS warehouse" these assets have a physical representation in game. In particular, +-- this means they can be destroyed during the transport, add more life to the DCS world etc. +-- +-- Or, in other words, on a scale between 1 and 10, how important do you reckon it is to have the right assets at the right place in a conflict? +-- +-- The warehouse adresses exactly this "problem" by simulating it in a realistic way while at the same time adding more life and dynamics to the DCS World. +-- +-- This comes along with some additional interesting stategic aspects since capturing/defending and destroying/protecting an enemy or your +-- own warehous becomes of critical importance for the development of a conflict. +-- +-- In essence, creating an efficient network of warehouses is vital for the success of a battle or even the whole war. Likewise, of course, cutting off the enemy +-- of important supply lines by capturing or destroying warehouses or their associated infrastructure is equally important. +-- +-- ## What is a warehouse? -- A warehouse is an abstract object represented by a physical (static) building that can hold virtual assets in stock. -- It can but it must not be associated with a particular airbase. The associated airbase can be an airdrome, a Helipad/FARP or a ship. -- --- If another another warehouse requests assets, the corresponding troops are spawned at the warehouse and being transported to the requestor or go their --- by themselfs. Once arrived at the requesting warehouse, the assets go into the stock of the requestor and can be reactivated when necessary. --- +-- If another warehouse requests assets, the corresponding troops are spawned at the warehouse and being transported to the requestor or go their +-- by themselfs. Once arrived at the requesting warehouse, the assets go into the stock of the requestor and can be activated/deployed when necessary. +-- -- ## 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 +-- Any kind of ground or airborne 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. -- --- # Creating a new warehouse +-- ## What means of transportation are available? +-- Firstly, all mobile assets can be send from warehouse to another on their own. -- --- A MOOSE warehouse must be represented in game by a phyical static object. For example, the mission editor already has warehouse as static object available. +-- * Ground vehicles will use the road infrastructure +-- * Airborne units are get a flightplan from the airbase the sending warehouse to the airbase of the receiving warehouse. This already implies that for airborne +-- assets both warehouses need an airbase. If either one of the warehouses does not have an associated airbase, transportation of airborne assest is not possible. +-- * Naval units can be exchanged between warehouses which posses a port/habour. Also shipping lanes must be specified manually but the user since DCS does not provide these. +-- * Trains use the available railroad infrastructure and both warehouses must have a connection to the railroad. Unfortunately, however, trains are not yet implemented to +-- a reasonable degree in DCS at the moment and hence cannot be used yet. +-- +-- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modelled +-- in a realistic way by using the corresponding cargo dispatcher classes, i.e. @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}, +-- @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} and @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}. +-- +-- # Creating a Warehouse +-- +-- A MOOSE warehouse must be represented in game by a phyical *static* object. For example, the mission editor already has warehouse as static object available. -- This would be a good first choice but any static object will do. -- -- The positioning of the warehouse static object is very important for a couple of reasons. Firtly, a warehouse needs a good infrastructure so that spawned assets @@ -93,7 +125,7 @@ -- -- # Adding Assets -- --- Assets can be added to the warehouse stock by using the @{#WAREHOUSE.AddAsset}(*group*, *ngroups*) function. The parameter *group* has to be a MOOSE @{Wrapper.Group#GROUP}. +-- 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. @@ -103,7 +135,10 @@ -- -- This will add five infantry groups to the warehouse stock. -- --- Note that you can also add assets with a delay by using the @{#WAREHOUSE.__AddAsset}(*delay*, *group*, *ngroups*), where *delay* is the delay in seconds before the asset is added. +-- 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. +-- +-- 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}. -- -- # Requesting Assets -- @@ -190,17 +225,17 @@ -- 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 -- for further tasking. Here, the groups are only smoked but, of course, you can use them for whatever task you imagine. -- --- Note that airborn groups are spawned in uncontrolled state and need to be activated first before they can start their assigned mission. +-- Note that airborne groups are spawned in uncontrolled state and need to be activated first before they can start their assigned mission. -- --- # Strategic Tasks +-- # Strategic Considerations -- --- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a juicy task for enemy attacks. +-- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a (potentially) juicy target for enemy attacks. -- There are several interesting situations, which can occurr. -- -- ## Capturing a Warehouse' Airbase -- --- If a warehouse has an associated airbase, it can be captured by the enemy. In this case, the warehouse looses it ability so employ all airborn assets and is also cut-off --- from supply by airborn units. +-- If a warehouse has an associated airbase, it can be captured by the enemy. In this case, the warehouse looses it ability so employ all airborne assets and is also cut-off +-- from supply by airborne units. -- -- Technically, the capturing of the airbase is triggered by the DCS S_EVENT_CAPTURE_BASE event. So the capturing takes place when only enemy ground units are in the -- airbase zone whilst no ground units of the present airbase owner are in that zone. @@ -213,7 +248,7 @@ -- -- ## Capturing the Warehouse -- --- A warehouse can also be captured by the enemy coaltion. If enemy groups enter the warehouse zone the event **Attacked** is triggered which can be captured by the +-- A warehouse can also be captured by the enemy coalition. If enemy ground troops enter the warehouse zone the event **Attacked** is triggered which can be captured by the -- @{#WAREHOUSE.OnAfterAttacked} event. -- -- If a warehouse is attacked it will spawn all its ground assets in the spawn zone which can than be used to defend the warehouse zone. @@ -230,12 +265,13 @@ -- gone as well. So a warehouse should be properly defended. -- -- Upon destruction of the warehouse, the event **Destroyed** is triggered, which can be captured by the @{#WAREHOUSE.OnAfterDestroyed} function. --- So the mission designer can invene at this point and for example choose to spawn all or paricular types of assets before the warehouse is gone for good. +-- So the mission designer can intervene at this point and for example choose to spawn all or paricular types of assets before the warehouse is gone for good. -- -- === -- -- # Examples -- +-- **WIP** -- -- -- @field #WAREHOUSE @@ -313,7 +349,6 @@ WAREHOUSE = { -- @field #string ATTRIBUTE Generalized attribute @{#WAREHOUSE.Attribute}. -- @field #string CATEGORY Asset category of type DCS#Group.Category, i.e. GROUND, AIRPLANE, HELICOPTER, SHIP, TRAIN. WAREHOUSE.Descriptor = { - --ID="id", TEMPLATENAME="templatename", UNITTYPE="unittype", ATTRIBUTE="attribute", @@ -382,15 +417,17 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.6w" +WAREHOUSE.version="0.2.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. +-- TODO: Add harbours and ports. +-- TODO: Add shipping lanes between warehouses. -- TODO: Set ROE for spawned groups. -- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. --- DONE: If warehouse is destoyed, all asssets are gone. -- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. -- TODO: Handle cases with immobile units. @@ -398,6 +435,10 @@ WAREHOUSE.version="0.2.6w" -- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? -- TODO: Add general message function for sending to coaliton or debug. -- TODO: Fine tune event handlers. +-- TODO: Add save/load capability of warehouse <==> percistance after mission restart. +-- TODO: Improve generalized attributes. +-- TODO: Add a time stamp when an asset is added to the stock and for requests +-- DONE: If warehouse is destoyed, all asssets are gone. -- DONE: Add event handlers. -- DONE: Add AI_CARGO_AIRPLANE -- DONE: Add AI_CARGO_APC @@ -444,7 +485,7 @@ function WAREHOUSE:New(warehouse, alias) -- Set some variables. self.warehouse=warehouse - self.uid=warehouse:GetID() + self.uid=tonumber(warehouse:GetID()) self.coalition=warehouse:GetCoalition() self.country=warehouse:GetCountry() self.coordinate=warehouse:GetCoordinate() @@ -465,31 +506,33 @@ function WAREHOUSE:New(warehouse, alias) self:SetStartState("Stopped") -- Add FSM transitions. - self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. - self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. - self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. - self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. - self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. - self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. - self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. - self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. - self:AddTransition("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("*", "Stop", "Stopped") -- TODO Stop the warehouse. - self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. - self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. - self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! - self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. - self:AddTransition("*", "AirbaseCaptured", "*") -- TODO Airbase was captured by other coalition. - self:AddTransition("*", "AirbaseRecaptured", "*") -- TODO Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. + self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. + self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. + self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. + self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. + self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. + self:AddTransition("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("*", "Stop", "Stopped") -- TODO Stop the warehouse. + self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. + self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. + self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! + self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. + self:AddTransition("*", "AirbaseCaptured", "*") -- TODO Airbase was captured by other coalition. + self:AddTransition("*", "AirbaseRecaptured", "*") -- TODO Airbase was re-captured from other coalition. + self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. - - -- Pseudo Functions + ------------------------ + --- Pseudo Functions --- + ------------------------ --- Triggers the FSM event "Start". Starts the warehouse. Initializes parameters and starts event handlers. -- @function [parent=#WAREHOUSE] Start @@ -528,7 +571,6 @@ function WAREHOUSE:New(warehouse, alias) -- @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 @@ -544,6 +586,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. + -- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. --- Trigger the FSM event "AddAsset" with a delay. Add an airplane group to the warehouse stock. -- @function [parent=#WAREHOUSE] __AddAsset @@ -551,6 +594,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. + -- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. --- Triggers the FSM event "AddRequest". Add a request to the warehouse queue, which is processed when possible. @@ -811,6 +855,13 @@ function WAREHOUSE:IsAttacked() return self:is("Attacked") end +--- Check if the warehouse is stopped. +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse is stopped. +function WAREHOUSE:IsStopped() + return self:is("Stopped") +end + --- Check if the warehouse has a road connection to another warehouse. Both warehouses need to be started! -- @param #WAREHOUSE self -- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. @@ -868,10 +919,10 @@ function WAREHOUSE:onafterStart(From, Event, To) text=text..string.format("Country = %d\n", self.country) text=text..string.format("Airbase = %s (%s)\n", tostring(self.airbase:GetName()), tostring(self.category)) env.info(text) - + -- Save self in static object. Easier to retrieve later. self.warehouse:SetState(self.warehouse, "WAREHOUSE", self) - + -- Set airbase name and category. if self.airbase and self.airbase:GetCoalition()==self.coalition then self.airbasename=self.airbase:GetName() @@ -880,11 +931,14 @@ function WAREHOUSE:onafterStart(From, Event, To) self.airbasename=nil self.category=-1 -- The -1 indicates that we dont have an airbase at this warehouse. end - + + -- THIS! caused aircraft to be spawned and started but they would never begin their route! + -- VERY strange. Need to test more. + --[[ -- Debug mark warehouse & spawn zone. self.zone:BoundZone(30, self.country) self.spawnzone:BoundZone(30, self.country) - + ]] -- Get the closest point on road wrt spawnzone of ground assets. local _road=self.spawnzone:GetCoordinate():GetClosestPointToRoad() @@ -932,6 +986,7 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Start the status monitoring. self:__Status(1) + end --- On after "Stop" event. Stops the warehouse, unhandles all events. @@ -1035,7 +1090,8 @@ end -- @param #string To To state. -- @param Wrapper.Group#GROUP group Group or template group to be added to the warehouse stock. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. -function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) +-- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. +function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribute) -- Set default. local n=ngroups or 1 @@ -1070,7 +1126,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) else -- This is a group that is not in the db yet. Add it n times. - local assets=self:_RegisterAsset(group, n) + local assets=self:_RegisterAsset(group, n, forceattribute) -- Add created assets to stock of this warehouse. for _,asset in pairs(assets) do @@ -1082,7 +1138,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups) -- TODO: This causes a problem, when a completely new asset is added, i.e. not from a template group. -- Need to create a "zombie" template group maybe? if group:IsAlive()==true then - env.info("FF destorying group "..group:GetName()) + self:E(self.wid..string.format("Destroying group %s.", group:GetName())) group:Destroy() end @@ -1117,8 +1173,9 @@ end -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that will be added to the warehouse stock. -- @param #number ngroups Number of groups to be added. +-- @param #string forceattribute Forced generalized attribute. -- @return #table A table containing all registered assets. -function WAREHOUSE:_RegisterAsset(group, ngroups) +function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) -- Set default. local n=ngroups or 1 @@ -1147,8 +1204,8 @@ function WAREHOUSE:_RegisterAsset(group, ngroups) local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(DCSdesc) - -- Get the generalized attribute. - local attribute=self:_GetAttribute(templategroupname) + -- Set/get the generalized attribute. + local attribute=forceattribute or self:_GetAttribute(templategroupname) -- Table for returned assets. local assets={} @@ -1219,8 +1276,9 @@ end -- @param #WAREHOUSE self -- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @param boolean aioff If true, AI of ground units are set to off. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetGround(asset, request) +function WAREHOUSE:_SpawnAssetGround(asset, request, aioff) if asset and asset.category==Group.Category.GROUND then @@ -1228,7 +1286,7 @@ function WAREHOUSE:_SpawnAssetGround(asset, request) local template=self:_SpawnAssetPrepareTemplate(asset, request) -- Initial spawn point. - template.route.points[1] = {} + template.route.points[1]={} -- Get a random coordinate in the spawn zone. local coord=self.spawnzone:GetRandomCoordinate() @@ -1262,8 +1320,13 @@ function WAREHOUSE:_SpawnAssetGround(asset, request) -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP - -- Activate group. - group:Activate() + -- Activate group. Should only be necessary for late activated groups. + --group:Activate() + + -- Switch AI off if desired. This works only for ground and naval groups. + if aioff then + group:SetAIOff() + end return group end @@ -1276,45 +1339,55 @@ end -- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. +-- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking) +function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then - -- Prepare spawn template. + -- Prepare the spawn template. local template=self:_SpawnAssetPrepareTemplate(asset, request) - -- Set and empty route. + -- Set route points. if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - -- Get flight path. + + -- Get flight path if the group goes to another warehouse by itself. template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) - --template.route.points[1] = {} + else - template.route.points[1] = {} + + -- First route point is the warehouse airbase. + template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, 0, true, self.airbase, nil, "Spawnpoint") + + --[[ + -- Link. + local spawnpoint=template.route.points[1] + + -- Set initial waypoint type/action ==> cold start. + spawnpoint.type = COORDINATE.WaypointType.TakeOffParking + spawnpoint.action = COORDINATE.WaypointAction.FromParkingArea + + --spawnpoint.type = COORDINATE.WaypointType.TakeOffParkingHot + --spawnpoint.action = COORDINATE.WaypointAction.FromParkingAreaHot + + -- Set airbase ID. + if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then + spawnpoint.helipadId = AirbaseID + spawnpoint.linkUnit = AirbaseID + spawnpoint.airdromeId = nil + elseif AirbaseCategory == Airbase.Category.AIRDROME then + spawnpoint.airdromeId = AirbaseID + spawnpoint.linkUnit = nil + spawnpoint.helipadId = nil + end + ]] + end - -- Link. - local spawnpoint=template.route.points[1] - - -- Set initial waypoint type/action ==> cold start. - spawnpoint.type = COORDINATE.WaypointType.TakeOffParking - spawnpoint.action = COORDINATE.WaypointAction.FromParkingArea - -- Get airbase ID and category. local AirbaseID = self.airbase:GetID() local AirbaseCategory = self.category - -- Set airbase ID. - if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then - spawnpoint.helipadId = AirbaseID - spawnpoint.linkUnit = AirbaseID - spawnpoint.airdromeId = nil - elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnpoint.airdromeId = AirbaseID - spawnpoint.linkUnit = nil - spawnpoint.helipadId = nil - end - -- Check enough parking spots. if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then --TODO Figure out what's necessary in this case. @@ -1351,7 +1424,7 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking) local coord=parking[i].Coordinate --Core.Point#COORDINATE local terminal=parking[i].TerminalID --#number - coord:MarkToAll(string.format("spawnplace unit %s terminal %d", unit.name, terminal)) + coord:MarkToAll(string.format("Spawnplace unit %s terminal %d", unit.name, terminal)) unit.x=coord.x unit.y=coord.z @@ -1364,27 +1437,26 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking) end -- Set general spawnpoint position. - local abc=self.airbase:GetCoordinate() - spawnpoint.x = template.units[1].x - spawnpoint.y = template.units[1].y - spawnpoint.alt = template.units[1].alt + --local abc=self.airbase:GetCoordinate() + --spawnpoint.x = template.units[1].x + --spawnpoint.y = template.units[1].y + --spawnpoint.alt = template.units[1].alt -- And template position. template.x = template.units[1].x template.y = template.units[1].y -- Uncontrolled spawning. - template.uncontrolled=true + template.uncontrolled=uncontrolled -- Debug info. - self:E({airtemplate=template}) + self:T2({airtemplate=template}) - -- Spawn group. local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP - -- Activate group. - group:Activate() + -- Activate group - should only be necessary for late activated groups. + --group:Activate() return group end @@ -1402,6 +1474,7 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, request) -- Create an own copy of the template! local template=UTILS.DeepCopy(asset.template) + --local template=asset.template -- Set unique name. template.name=self:_Alias(asset, request) @@ -1412,12 +1485,18 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, request) -- Nillify the group ID. template.groupId=nil + + -- For group units, visible needs to be false. + if asset.category==Group.Category.GROUND then + template.visible=false + end -- No late activation. template.lateActivation=false -- Set and empty route. template.route = {} + template.route.routeRelativeTOT=true template.route.points = {} -- Handle units. @@ -1429,7 +1508,7 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, request) -- Nillify the unit ID. unit.unitId=nil - -- Set unit name. + -- Set unit name: -01, -02, ... unit.name=string.format("%s-%02d", template.name , i) end @@ -1606,40 +1685,43 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- No transport unit requested. Assets go by themselfes. if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - env.info("FF selfpropelled") + self:I(self.wid..string.format("Got selfpropelled request for %d assets.",_spawngroups:Count())) for _,_spawngroup in pairs(_spawngroups:GetSetObjects()) do + -- Group intellisense. local group=_spawngroup --Wrapper.Group#GROUP -- Route cargo to their destination. if _cargocategory==Group.Category.GROUND then - env.info("FF route ground "..group:GetName()) + self:I(self.wid..string.format("Route ground group %s.", group:GetName())) -- Random place in the spawn zone of the requesting warehouse. local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate() - ToCoordinate:MarkToAll("Destination") + ToCoordinate:MarkToAll(string.format("Destination of group %s", group:GetName())) -- Route ground. self:_RouteGround(group, Request) - elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - env.info("FF route aircraft "..group:GetName()) + elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + self:I(self.wid..string.format("Route airborne group %s.", group:GetName())) -- Route plane the the requesting warehouses airbase. -- Actually, the route is already set. We only need to activate the uncontrolled group. self:_RouteAir(group, Request.airbase) elseif _cargocategory==Group.Category.SHIP then + self:E("ERROR: self propelled ship not implemented yet!") + elseif _cargocategory==Group.Category.TRAIN then - env.info("FF route train "..group:GetName()) + self:I(self.wid..string.format("Route train group %s.", group:GetName())) -- Route train to the rail connection of the requesting warehouse. self:_RouteTrain(group, Request.warehouse.rail) else - self:E(self.wid..string.format("ERROR: unknown category %s for self propelled cargo %s!",tostring(_cargocategory), tostring(group:GetName()))) + self:E(self.wid..string.format("ERROR: unknown category %s for self propelled cargo %s!", tostring(_cargocategory), tostring(group:GetName()))) end end @@ -1732,7 +1814,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _alias=self:_Alias(_assetitem, Request) -- Spawn plane at airport in uncontrolled state. - local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid]) + local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid], true) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -1768,8 +1850,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn with ALIAS here or DCS crashes! local _alias=self:_Alias(_assetitem, Request) - -- Spawn plane at airport in uncontrolled state. - local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid]) + -- Spawn plane at airport in controlled state. They need to fly to the spawn zone. + local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid], false) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -1795,7 +1877,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Home zone. --CargoTransport:Setairbase(self.airbase) - --CargoTransport:SetHomeZone(self.spawnzone) + CargoTransport:SetHomeZone(self.spawnzone) elseif Request.transporttype==WAREHOUSE.TransportType.APC then ----------- @@ -1861,12 +1943,12 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --- 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}) + self:I("FF OnAfterUnloaded:") + self:I({From=From}) + self:I({Event=Event}) + self:I({To=To}) + self:I({Carrier=Carrier}) + self:I({Cargo=Cargo}) -- Get group obejet. local group=Cargo:GetObject() --Wrapper.Group#GROUP @@ -1970,16 +2052,22 @@ function WAREHOUSE:_SpawnAssetRequest(Request) --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! -- Spawn air units. - _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[_assetitem.uid]) + _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[_assetitem.uid], UnControlled) elseif _assetitem.category==Group.Category.TRAIN then -- Spawn train. if self.rail then --TODO: Rail should only get one asset because they would spawn on top! - _group=_spawn:SpawnFromCoordinate(self.rail) + --_group=_spawn:SpawnFromCoordinate(self.rail) end + self:E(self.wid.."ERROR: Spawning of TRAIN assets not possible yet!") + + elseif _assetitem.category==Group.Category.SHIP then + self:E(self.wid.."ERROR: Spawning of SHIP assets not possible yet!") + else + end -- Add group to group set and asset list. @@ -2170,10 +2258,10 @@ end -- @param #WAREHOUSE.Pendingitem request Pending self request. function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) - self:E(self.wid..string.format("Assets spawned at warehouse %s after self request!", self.alias)) + -- Debug info. + self:I(self.wid..string.format("Assets spawned at warehouse %s after self request!", self.alias)) - env.info("FF spawned groupas at self request") - -- Put assets in new warehouse. + -- Debug info. for _,_group in pairs(groupset:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP local text=string.format("Group name = %s, IsAlive=%s.", tostring(group:GetName()), tostring(group:IsAlive())) @@ -2339,9 +2427,6 @@ function WAREHOUSE:_RouteGround(group, request) local _speed=group:GetSpeedMax()*0.7 -- Create task. - -- DONE: It might be necessary to ALWAYS route the group to the road connection first. - -- At the moment, the random spawn point might give another first road point which could also be a dead end like in Kobuliti(?). - -- TODO: Might be necessary to include the current coordinate as first waypoint?! -- Waypoints for road-to-road connection. local Waypoints, canroad = group:TaskGroundOnRoad(request.warehouse.road, _speed, "Off Road", false, self.road) @@ -2371,24 +2456,28 @@ function WAREHOUSE:_RouteGround(group, request) end ---- Route the airplane from one airbase another. +--- Route the airplane from one airbase another. Activates uncontrolled aircraft and sets ROE/ROT for ferry flights. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP Aircraft Airplane group to be routed. function WAREHOUSE:_RouteAir(aircraft) if aircraft and aircraft:IsAlive()~=nil then - -- Start command. - local StartCommand = {id = 'Start', params = {}} - aircraft:SetCommand(StartCommand) + -- Debug info. + self:T2(self.wid..string.format("RouteAir aircraft group %s alive=%s", aircraft:GetName(), tostring(aircraft:IsAlive()))) + + -- Give start command to activate uncontrolled aircraft. + aircraft:SetCommand({id='Start', params={}}) + -- Debug info. + self:T2(self.wid..string.format("RouteAir aircraft group %s alive=%s (after start command)", aircraft:GetName(), tostring(aircraft:IsAlive()))) + -- Set ROE and alaram state. aircraft:OptionROEReturnFire() aircraft:OptionROTPassiveDefense() - else - self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(Aircraft:GetName()), tostring(Aircraft:IsAlive()))) + self:E(string.format("ERROR: aircraft %s cannot be routed since it does not exist or is not alive %s!", tostring(aircraft:GetName()), tostring(aircraft:IsAlive()))) end end @@ -2422,9 +2511,9 @@ end -- @param Wrapper.Group#GROUP group The group that arrived. -- @param #WAREHOUSE self function WAREHOUSE._Arrived(group, warehouse) - env.info(warehouse.wid..string.format("Group %s arrived", tostring(group:GetName()))) + env.info(warehouse.wid..string.format("Group %s arrived at destination.", tostring(group:GetName()))) - --Trigger delivered event. + --Trigger "Arrived" event. warehouse:__Arrived(1, group) end @@ -2438,7 +2527,7 @@ function WAREHOUSE:_ArrivedSimple(group) --local self:_GetIDsFromGroup(group) env.info(self.wid..string.format("Group %s arrived at warehouse ", tostring(group:GetName()))) - --Trigger delivered event. + --Trigger "Arrived event. self:__Arrived(1, group) end @@ -2484,7 +2573,7 @@ function WAREHOUSE:_OnEventArrived(EventData) self:__Arrived(dt, group) else - self:E(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.", tostring(wid), tostring(aid), tostring(rid))) + self:T3(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.", tostring(wid), tostring(aid), tostring(rid))) end end end @@ -2495,13 +2584,17 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBirth(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event birth!", self.alias)) + self:T3(self.wid..string.format("Warehouse %s (id=%s) captured event birth!", self.alias, self.uid)) if EventData and EventData.IniGroup then local group=EventData.IniGroup + -- env.info(string.format("FF birth of group %s (alive=%s) unit %s", tostring(EventData.IniGroupName), tostring(EventData.IniGroup:IsAlive()), tostring(EventData.IniUnitName))) + -- Note: Remember, group:IsAlive might(?) not return true here. local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then self:E(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s.", self.alias, EventData.IniUnitName)) + else + --self:T3({wid=wid, uid=self.uid, match=(wid==self.uid), tw=type(wid), tu=type(self.uid)}) end end end @@ -2570,7 +2663,7 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventCrashOrDead(EventData) - self:E(self.wid..string.format("Warehouse %s captured event dead or crash!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event dead or crash!",self.alias)) if EventData and EventData.IniUnit then @@ -2581,6 +2674,15 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) self:Destroyed() end end + + -- Check if an asset unit was destroyed. + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:E(self.wid..string.format("Warehouse %s captured event dead or crash of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end end @@ -2778,7 +2880,7 @@ end -- @param #table queue The queue which is holding the requests to check. -- @return #boolean If true, request can be executed. If false, something is not right. function WAREHOUSE:_CheckRequestConsistancy(queue) - env.info("FF checking request consistancy!") + self:T3(self.wid..string.format("Number of queued requests = %d", #queue)) -- Requests to delete. local invalid={} @@ -2786,6 +2888,9 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) for _,_request in pairs(queue) do local request=_request --#WAREHOUSE.Queueitem + -- Debug info. + self:T2(self.wid..string.format("Checking request = %d.", request.uid)) + -- Let's assume everything is fine. local valid=true @@ -2803,9 +2908,16 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Request from enemy coalition? if self.coalition~=request.warehouse.coalition then self:E(self.wid..string.format("ERROR: Incorrect request. Requesting warehouse is of wrong coaltion! Own coalition %d. Requesting warehouse %d", self.coalition, request.warehouse.coalition)) + valid=false + end + + -- Is receiving warehouse stopped? + if request.warehouse:IsStopped() then + self:E(self.wid..string.format("ERROR: Incorrect request. Requesting warehouse is stopped!")) valid=false end - + + -- Asset is air, ground etc. local asset_air=false local asset_plane=false local asset_helo=false @@ -2830,7 +2942,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Only one train due to finding spawn placen on rail! --nAsset=1 else - self:E("ERROR: incorrect request. Asset Descriptor missmatch! Has to be Group.Cagetory.AIRPLANE, ...") + self:E("ERROR: Incorrect request. Asset Descriptor missmatch! Has to be Group.Cagetory.AIRPLANE, ...") valid=false end @@ -2851,7 +2963,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) elseif request.assetdescval==WAREHOUSE.Attribute.INFANTRY then asset_ground=true elseif request.assetdescval==WAREHOUSE.Attribute.OTHER then - self:E("ERROR: incorrect request. Asset attribute WAREHOUSE.Attribute.OTHER is not valid!") + self:E("ERROR: Incorrect request. Asset attribute WAREHOUSE.Attribute.OTHER is not valid!") valid=false elseif request.assetdescval==WAREHOUSE.Attribute.SHIP then asset_naval=true @@ -2870,7 +2982,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) elseif request.assetdescval==WAREHOUSE.Attribute.TRUCK then asset_ground=true else - self:E("ERROR: incorrect request. Unknown asset attribute!") + self:E("ERROR: Incorrect request. Unknown asset attribute!") valid=false end end @@ -2882,13 +2994,14 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) ------------------------------------------- -- Case where the units go my themselves -- ------------------------------------------- + if asset_air then if asset_plane then -- No airplane to or from FARPS. if request.category==Airbase.Category.HELIPAD or self.category==Airbase.Category.HELIPAD then - self:E("ERROR: incorrect request. Asset aircraft requestst but warehouse or requestor is HELIPAD/FARP!") + self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!") valid=false end @@ -2897,10 +3010,10 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) elseif asset_helo then - -- Helos need a FARP or AIRBASE or SHIP for spawning. Event if they go there they "cannot" be spawned again. + -- Helos need a FARP or AIRBASE or SHIP for spawning. Also at the the receiving warehouse. So even if they could go there they "cannot" be spawned again. -- Unless I allow spawning of helos in the the spawn zone. But one should place at least a FARP there. if self.category==-1 or request.category==-1 then - self:E("ERROR: incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destinaion base!") + self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!") valid=false end @@ -2908,7 +3021,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- All aircraft need an airbase of any type as depature or destination. if self.airbase==nil or request.airbase==nil then - self:E("ERROR: incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!") + self:E("ERROR: Incorrect request. Either warehouse or requesting warehouse does not have any kind of airbase!") valid=false end @@ -2917,57 +3030,42 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- No ground assets directly to or from ships. -- TODO: May needs refinement if warehouse is on land and requestor is ship in harbour?! if (request.category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) then - self:E("ERROR: incorrect request. Ground asset requested but warehouse or requestor is SHIP!") + self:E("ERROR: Incorrect request. Ground asset requested but warehouse or requestor is SHIP!") valid=false end if asset_train then -- Check if there is a valid path on rail. - valid=self:HasConnectionRail(request.warehouse) - --[[ - if self.rail and request.warehouse.rail then - local onrail=self.rail:GetPathOnRoad(request.warehouse.rail, false, true) - if onrail==nil then - self:E("ERROR: incorrect request. No valid path on rail for train assets!") - valid=false - end - else - self:E("ERROR: incorrect request. Either warehouse or requesting warehouse have no connection to rail!") - valid=false + local hasrail=self:HasConnectionRail(request.warehouse) + if not hasrail then + self:E("ERROR: Incorrect request. No valid path on rail for train assets!") + valid=false end - ]] else -- Check if there is a valid path on road. - valid=self:HasConnectionRoad(request.warehouse) - --[[ - if self.road and request.warehouse.road then - local onroad=self.road:GetPathOnRoad(request.warehouse.road, false, false) - if onroad==nil then - self:E("ERROR: incorrect request. No valid path on road for ground assets!") - valid=false - end - else - self:E("ERROR: incorrect request. Either warehouse or requesting warehouse have no connection to road!") - valid=false - end - ]] + local hasroad=self:HasConnectionRoad(request.warehouse) + if not hasroad then + self:E("ERROR: Incorrect request. No valid path on road for ground assets!") + valid=false + end end elseif asset_naval then - self:E("ERROR: incorrect request. Naval units not supported yet!") + self:E("ERROR: Incorrect request. Naval units not supported yet!") valid=false end - else - - -- Assests need a transport. - + else + ------------------------------- + -- Assests need a transport --- + ------------------------------- + if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then -- Airplanes only to AND from airdromes. if self.category~=Airbase.Category.AIRDROME or request.category~=Airbase.Category.AIRDROME then - self:E("ERROR: incorrect request. Warehouse or requestor does not have an airdrome. No transport by plane possible!") + self:E("ERROR: Incorrect request. Warehouse or requestor does not have an airdrome. No transport by plane possible!") valid=false end @@ -2979,44 +3077,51 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- No transport to or from ships if self.category==Airbase.Category.SHIP or request.category==Airbase.Category.SHIP then - self:E("ERROR: incorrect request. Warehouse or requestor is SHIP. No transport by APC possible!") + self:E("ERROR: Incorrect request. Warehouse or requestor is SHIP. No transport by APC possible!") + valid=false + end + + -- Check if there is a valid path on road. + local hasroad=self:HasConnectionRoad(request.warehouse) + if not hasroad then + self:E("ERROR: Incorrect request. No valid path on road for ground transport assets!") valid=false end elseif request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - -- Transport by helicopters ==> need airbase for spawning but not for delivering to the zone. + -- Transport by helicopters ==> need airbase for spawning but not for delivering to the spawn zone of the receiver. if self.category==-1 then - self:E("ERROR: incorrect request. Warehouse has no airbase. Transport by helicopter not possible!") + self:E("ERROR: Incorrect request. Warehouse has no airbase. Transport by helicopter not possible!") valid=false end elseif request.transporttype==WAREHOUSE.TransportType.SHIP then -- Transport by ship. - self:E("ERROR: incorrect request. Transport by SHIP not implemented yet!") + self:E("ERROR: Incorrect request. Transport by SHIP not implemented yet!") valid=false elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then - -- Only one train due to limited spawn place. - --nTransport=1 - -- Transport by train. - self:E("ERROR: incorrect request. Transport by TRAIN not implemented yet!") + self:E("ERROR: Incorrect request. Transport by TRAIN not implemented yet!") valid=false else -- No match. - self:E("ERROR: incorrect request. Transport type unknown!") + self:E("ERROR: Incorrect request. Transport type unknown!") valid=false end end -- Add request as unvalid and delete it later. - if not valid then + if valid==false then + self:E(self.wid..string.format("Got invalid request id=%d.", request.uid)) table.insert(invalid, request) + else + self:T3(self.wid..string.format("Got valid request id=%d.", request.uid)) end end -- loop queue items. @@ -3024,7 +3129,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Delete invalid requests. for _,_request in pairs(invalid) do - self:E(self.wid..string.format("Deleting invalid request %d",_request.uid)) + self:E(self.wid..string.format("Deleting invalid request id=%d.",_request.uid)) self:_DeleteQueueItem(_request, self.queue) end @@ -3037,14 +3142,23 @@ end -- @return #boolean If true, request can be executed. If false, something is not right. function WAREHOUSE:_CheckRequestNow(request) + -- Assume request is okay and check scenarios. local okay=true + -- Check if receiving warehouse is running. + if not request.warehouse:IsRunning() then + local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.", self.alias, request.warehouse.alias, request.warehouse:GetState()) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + okay=false + end + -- Check if number of requested assets is in stock. local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) -- Check if enough assets are in stock. if not _enough then - local text=string.format("Request denied! Not enough (cargo) assets currently available.") + local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) okay=false @@ -3061,7 +3175,7 @@ function WAREHOUSE:_CheckRequestNow(request) if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then local Parking=self:_FindParkingForAssets(self.airbase,_assets) if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all assets at the moment.") + local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all assets at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) okay=false @@ -3078,7 +3192,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check if enough transport units are available. if not _enough then - local text=string.format("Request denied! Not enough transport units currently available.") + local text=string.format("Warehouse %s: Request denied! Not enough transport assets currently available.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) okay=false @@ -3095,7 +3209,7 @@ function WAREHOUSE:_CheckRequestNow(request) if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then local Parking=self:_FindParkingForAssets(self.airbase,_transports) if Parking==nil then - local text=string.format("Request denied! Not enough free parking spots for all transports at the moment.") + local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) okay=false @@ -3237,7 +3351,8 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local _vec3=static:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) local _name=static:getName() - local _size=self:_GetObjectSize(static:GetDCSObject()) + --env.info("FF static name = "..tostring(_name)) + local _size=self:_GetObjectSize(static) table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="static"}) end @@ -3246,7 +3361,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local _vec3=scenery:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) local _name=scenery:getTypeName() - local _size=self:_GetObjectSize(scenery:GetDCSObject()) + local _size=self:_GetObjectSize(scenery) table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="scenery"}) end @@ -3818,7 +3933,7 @@ end -- @return #table Table of flightplan coordinates. function WAREHOUSE:_GetFlightplan(asset, departure, destination) - -- Parameters + -- Parameters in SI units. local Vmax=asset.speedmax/3.6 local Range=asset.range local _category=asset.category diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 081c632bf..8b7f34a26 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -661,8 +661,9 @@ function GROUP:GetDCSUnits() end ---- Activates a GROUP. +--- Activates a late activated GROUP. -- @param #GROUP self +-- @return #GROUP self function GROUP:Activate() self:F2( { self.GroupName } ) trigger.action.activateGroup( self:GetDCSObject() ) diff --git a/Moose Development/Moose/Wrapper/Object.lua b/Moose Development/Moose/Wrapper/Object.lua index f67ec389e..f57ff73e8 100644 --- a/Moose Development/Moose/Wrapper/Object.lua +++ b/Moose Development/Moose/Wrapper/Object.lua @@ -54,8 +54,7 @@ end --- Returns the unit's unique identifier. -- @param Wrapper.Object#OBJECT self --- @return DCS#Object.ID ObjectID --- @return #nil The DCS Object is not existing or alive. +-- @return DCS#Object.ID ObjectID or #nil if the DCS Object is not existing or alive. Note that the ID is passed as a string and not a number. function OBJECT:GetID() local DCSObject = self:GetDCSObject() From 508e35aec3a21078051295b530e5abc86a03eb99 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 27 Aug 2018 00:10:05 +0200 Subject: [PATCH 30/73] Warehouse v0.2.7 --- Moose Development/Moose/AI/AI_Cargo_APC.lua | 2 + .../Moose/AI/AI_Cargo_Dispatcher.lua | 7 +- .../Moose/AI/AI_Cargo_Helicopter.lua | 25 ++- Moose Development/Moose/Core/Zone.lua | 1 + .../Moose/Functional/Warehouse.lua | 208 +++++++++++------- .../Moose/Wrapper/Controllable.lua | 9 +- Moose Development/Moose/Wrapper/Group.lua | 3 +- 7 files changed, 158 insertions(+), 97 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index 47b900cd0..58a2e7589 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -804,6 +804,7 @@ end -- @param #AI_CARGO_APC self function AI_CARGO_APC._BackHome(APC, self) --Trigger BackHome event. + env.info(string.format("FF APC %s is back home task function!",APC:GetName())) APC:SmokeGreen() self:__BackHome(1) end @@ -815,5 +816,6 @@ end -- @param Event -- @param To function AI_CARGO_APC:onafterBackHome( APC, From, Event, To ) + env.info(string.format("FF APC %s is back home event!",APC:GetName())) APC:SmokeRed() end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 5bda17ffe..05264bfa6 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -402,7 +402,12 @@ function AI_CARGO_DISPATCHER:onafterMonitor() function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo ) self:Unloaded( Carrier, Cargo ) - end + end + + -- FF added back home event. + function AI_Cargo.OnAfterBackHome( AI_Cargo, Carrier, From, Event, To) + self:BackHome( Carrier ) + end end -- The Pickup sequence ... diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 540d4b064..e115a09dc 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -241,8 +241,11 @@ function AI_CARGO_HELICOPTER:onafterLanded( Helicopter, From, Event, To ) end end - if self.RouteDeploy == true then - if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then + if self.RouteDeploy == true then + local height=Helicopter:GetHeight( true ) + local velocity=Helicopter:GetVelocityKMH() + env.info(string.format("FF helo in air %s, height = %d m, velocity = %d km/h", tostring(Helicopter:InAir()), height, velocity)) + if height <= 10 and velocity < 10 then self:Unload( true ) self.RouteDeploy = false self.Transporting = false @@ -823,15 +826,21 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... Helicopter:WayPointInitialize( Route ) - local Tasks = {} - + local Tasks = {} Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) - Tasks[#Tasks+1] = Helicopter:TaskFunction("AI_CARGO_HELICOPTER._BackHome", self) - Route[#Route].task = Helicopter:TaskCombo( Tasks ) - - Route[#Route+1] = WaypointTo + -- FF + --[[ + local Tasks2 = {} + Tasks2[#Tasks2+1] = Helicopter:TaskFunction("AI_CARGO_HELICOPTER._BackHome", self) + + Route[#Route+1] = WaypointTo + Route[#Route].task = Helicopter:TaskCombo( Tasks2 ) + -- FF + ]] + + Route[#Route+1] = WaypointTo -- Now route the helicopter Helicopter:Route( Route, 0 ) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 207587aee..4d28f6ebc 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1078,6 +1078,7 @@ function ZONE_UNIT:GetVec2() local ZoneVec2 = self.ZoneUNIT:GetVec2() if ZoneVec2 then + local heading if self.relative_to_unit then heading = ( self.ZoneUNIT:GetHeading() or 0.0 ) * math.pi / 180.0 else diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0be2f17a5..c9fca1ca5 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -69,7 +69,7 @@ -- -- The MOOSE warehouse adds a new logistic component to the DCS World. *Assets*, i.e. ground, airborne and naval units, can be transferred from one place -- to another in a realistic and highly automatic fashion. In contrast to a "DCS warehouse" these assets have a physical representation in game. In particular, --- this means they can be destroyed during the transport, add more life to the DCS world etc. +-- this means they can be destroyed during the transport and add more life to the DCS world. -- -- Or, in other words, on a scale between 1 and 10, how important do you reckon it is to have the right assets at the right place in a conflict? -- @@ -417,7 +417,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.6" +WAREHOUSE.version="0.2.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -503,32 +503,32 @@ function WAREHOUSE:New(warehouse, alias) self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 200) -- Start State. - self:SetStartState("Stopped") + self:SetStartState("NotReadyYet") -- Add FSM transitions. - -- From State --> Event --> To State - self:AddTransition("Stopped", "Load", "Stopped") -- TODO Load the warehouse state. No sure if it should be in stopped state. - self:AddTransition("Stopped", "Start", "Running") -- Start the warehouse. - self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. - self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. - self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. - self:AddTransition("*", "Arrived", "*") -- Cargo group has arrived at destination. - self:AddTransition("*", "Delivered", "*") -- All cargo groups of a request have been delivered to the requesting warehouse. - self:AddTransition("Running", "SelfRequest", "*") -- Request to warehouse itself. Requested assets are only spawned but not delivered anywhere. - self:AddTransition("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("*", "Stop", "Stopped") -- TODO Stop the warehouse. - self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. - self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. - self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! - self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. - self:AddTransition("*", "AirbaseCaptured", "*") -- TODO Airbase was captured by other coalition. - self:AddTransition("*", "AirbaseRecaptured", "*") -- TODO Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. + -- From State --> Event --> To State + self:AddTransition("NotReadyYet", "Load", "NotReadyYet") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("NotReadyYet", "Start", "Running") -- Start the warehouse. + self:AddTransition("*", "Status", "*") -- Status update. + self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. + self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. + self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Arrived", "*") -- Cargo or transport group has arrived. + 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("*", "Stop", "Stopped") -- TODO Stop the warehouse. + self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. + self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. + self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! + self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. + self:AddTransition("*", "AirbaseCaptured", "*") -- TODO Airbase was captured by other coalition. + self:AddTransition("*", "AirbaseRecaptured", "*") -- TODO Airbase was re-captured from other coalition. + self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. ------------------------ --- Pseudo Functions --- @@ -1356,31 +1356,20 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) else - -- First route point is the warehouse airbase. - template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, 0, true, self.airbase, nil, "Spawnpoint") - - --[[ - -- Link. - local spawnpoint=template.route.points[1] + local hotstart=true - -- Set initial waypoint type/action ==> cold start. - spawnpoint.type = COORDINATE.WaypointType.TakeOffParking - spawnpoint.action = COORDINATE.WaypointAction.FromParkingArea + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea - --spawnpoint.type = COORDINATE.WaypointType.TakeOffParkingHot - --spawnpoint.action = COORDINATE.WaypointAction.FromParkingAreaHot - - -- Set airbase ID. - if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then - spawnpoint.helipadId = AirbaseID - spawnpoint.linkUnit = AirbaseID - spawnpoint.airdromeId = nil - elseif AirbaseCategory == Airbase.Category.AIRDROME then - spawnpoint.airdromeId = AirbaseID - spawnpoint.linkUnit = nil - spawnpoint.helipadId = nil + -- Hot start. + if hotstart then + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot end - ]] + + -- First route point is the warehouse airbase. + template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action, 0, true, self.airbase, nil, "Spawnpoint") end @@ -1963,9 +1952,17 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --- On after BackHome event. function CargoTransport:OnAfterBackHome(From, Event, To, Carrier) + -- Intellisense. + local carrier=Carrier --Wrapper.Group#GROUP + -- Get warehouse state. - local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - Carrier:SmokeRed() + local warehouse=carrier:GetState(carrier, "WAREHOUSE") --#WAREHOUSE + carrier:SmokeWhite() + + -- Debug info. + local text=string.format("Carrier %s is back home at warehouse %s.", tostring(Carrier:GetName()), tostring(warehouse.warehouse:GetName())) + MESSAGE:New(text, 5):ToAllIf(warehouse.Debug) + warehouse:I(warehouse.wid..text) -- Add carrier back to warehouse stock. Actual unit is destroyed. warehouse:AddAsset(Carrier) @@ -2139,7 +2136,7 @@ end -- @param Wrapper.Group#GROUP group The group that was delivered. function WAREHOUSE:onafterArrived(From, Event, To, group) - self:E(self.wid..string.format("Cargo %s arrived!", tostring(group:GetName()))) + self:I(self.wid..string.format("Cargo %s arrived!", tostring(group:GetName()))) group:SmokeOrange() -- Update pending request. @@ -2150,8 +2147,10 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) -- Number of cargo assets still in group set. local ncargo=request.cargogroupset:Count() - -- Info - self:E(self.wid..string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo)) + -- Debug message. + local text=string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Debug) + self:I(self.wid..text) -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. if group:IsGround() and group:GetSpeedMax()>1 then @@ -2229,18 +2228,13 @@ end function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Debug info - self:E(self.wid..string.format("All assets from warehouse %s delivered to warehouse %s!", self.alias, request.warehouse.alias)) - + local text=string.format("Warehouse %s: All assets delivered to warehouse %s!", self.alias, request.warehouse.alias) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) + + -- Make some noice :) self:_Fireworks(request.warehouse.coordinate) - --[[ - -- Fireworks! - for i=1,91 do - local color=math.random(0,3) - request.warehouse.coordinate:Flare(color, i-1) - end - ]] - -- Remove pending request: self:_DeleteQueueItem(request, self.pending) @@ -2287,7 +2281,11 @@ end -- @param DCS#coalition.side Coalition which is attacking the warehouse. -- @param DCS#country.id Country which is attacking the warehouse. function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) - self:E(self.wid..string.format("Our warehouse is under attack!")) + + -- Warning. + local text=string.format("Warehouse %s: We are under attack!", self.alias) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) -- Spawn all ground units in the spawnzone? self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) @@ -2299,7 +2297,11 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterDefeated(From, Event, To) - self:E(self.wid..string.format("Attack was defeated!")) + + -- Message. + local text=string.format("Warehouse %s: Enemy attack was defeated!", self.alias) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) --if self.defenderrequest then for _,request in pairs(self.defending) do @@ -2339,7 +2341,11 @@ end -- @param DCS#coalition.side Coalition which captured the warehouse. -- @param DCS#country.id Country which has captured the warehouse. function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) - self:E(self.wid..string.format("Our warehouse was captured by coalition %d!", Coalition)) + + -- Message. + local text=string.format("Warehouse %s: We were captured by enemy coalition (%d)!", self.alias, Coalition) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) -- Respawn warehouse with new coalition/country. self.warehouse:ReSpawn(Country) @@ -2379,8 +2385,16 @@ end -- @param #string To To state. -- @param DCS#coalition.side Coalition which captured the warehouse. function WAREHOUSE:onafterAirbaseCaptured(From, Event, To, Coalition) - self:E(self.wid..string.format("Our airbase %s was captured by coalition %d!", self.airbasename, Coalition)) + + -- Message. + local text=string.format("Warehouse %s: Our airbase %s was captured by the enemy (coalition=%d)!", self.alias, self.airbasename, Coalition) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) + + -- Debug smoke. self.airbase:GetCoordinate():SmokeRed() + + -- Set airbase to nil and category to no airbase. self.airbase=nil self.category=-1 -- -1 indicates no airbase. end @@ -2392,9 +2406,17 @@ end -- @param #string To To state. -- @param DCS#coalition.side Coalition which captured the warehouse. function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) - self:E(self.wid..string.format("We re-capturd our airbase %s from coalition %d!", self.airbasename, Coalition)) + + -- Message. + local text=string.format("Warehouse %s: We recaptured our airbase %d from the enemy (coalition=%d)!", self.alias, self.airbasename, Coalition) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) + + -- Set airbase and category. self.airbase=AIRBASE:FindByName(self.airbasename) self.category=self.airbase:GetDesc().category + + -- Debug smoke. self.airbase:GetCoordinate():SmokeGreen() end @@ -2405,7 +2427,12 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterDestroyed(From, Event, To) - self:E(self.wid..string.format("Our warehouse was destroyed!")) + + -- Message. + local text=string.format("Warehouse %s was destroyed!", self.alias) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) + -- Stop warehouse FSM. self:Stop() end @@ -2563,7 +2590,9 @@ function WAREHOUSE:_OnEventArrived(EventData) if wid~=nil and aid~=nil and rid~=nil then -- Debug info. - self:E(self.wid..string.format("Air asset group %s arrived.", group:GetName())) + local text=string.format("Air asset group %s arrived at warehouse %s.", group:GetName(), self.alias) + --MESSAGE:New + self:E(self.wid..text) -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since @@ -2640,6 +2669,24 @@ function WAREHOUSE:_OnEventLanding(EventData) local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then self:E(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) + + -- Get request of this group + local request=self:_GetRequestOfGroup(group,self.pending) + + -- If request is nil, the cargo has been delivered. + -- TODO: I might need to add a delivered table, to be better able to get this right. + if request==nil then + + -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. + if group:GetCategory()==Group.Category.HELICOPTER then + if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then + group:SmokeWhite() + self:__AddAsset(30, group) + end + end + + end + end end end @@ -2772,7 +2819,7 @@ function WAREHOUSE:_CheckConquered() local _country=unit:GetCountry() -- Debug info. - self:E(self.wid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(), radius,_coalition,_country, distance)) + self:T2(self.wid..string.format("Unit %s in warehouse zone of radius=%d m. Coalition=%d, country=%d. Distance = %d m.",unit:GetName(), radius,_coalition,_country, distance)) -- Add up units for each side. if _coalition==coalition.side.BLUE then @@ -2791,7 +2838,7 @@ function WAREHOUSE:_CheckConquered() end -- Debug info. - self:E(self.wid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d", Nblue, Nred, Nneutral)) + self:T(self.wid..string.format("Ground troops in warehouse zone: blue=%d, red=%d, neutral=%d", Nblue, Nred, Nneutral)) -- Figure out the new coalition if any. @@ -2911,7 +2958,8 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) valid=false end - -- Is receiving warehouse stopped? + -- Is receiving warehouse stopped? Actually, running state is checked later. So I leave this for now in case the warehouse was not started yet. + -- TODO: Introduce another FSM start state to be able to distinguish between a warehouse that was not started yet or was started and then stopped! if request.warehouse:IsStopped() then self:E(self.wid..string.format("ERROR: Incorrect request. Requesting warehouse is stopped!")) valid=false @@ -3031,7 +3079,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- TODO: May needs refinement if warehouse is on land and requestor is ship in harbour?! if (request.category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) then self:E("ERROR: Incorrect request. Ground asset requested but warehouse or requestor is SHIP!") - valid=false + --valid=false end if asset_train then @@ -3042,11 +3090,13 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) valid=false end else - -- Check if there is a valid path on road. - local hasroad=self:HasConnectionRoad(request.warehouse) - if not hasroad then - self:E("ERROR: Incorrect request. No valid path on road for ground assets!") - valid=false + if self.warehouse:GetName()~=request.warehouse.warehouse:GetName() then + -- Check if there is a valid path on road. + local hasroad=self:HasConnectionRoad(request.warehouse) + if not hasroad then + self:E("ERROR: Incorrect request. No valid path on road for ground assets!") + valid=false + end end end elseif asset_naval then diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index b607b4643..d5bff0bc0 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1605,7 +1605,7 @@ end -- RouteToZone( GroundGroup, ZoneList[1] ) -- function CONTROLLABLE:TaskFunction( FunctionString, ... ) - self:F2( { FunctionString, arg } ) + self:E({TaskFunction=FunctionString, arguments=arg}) local DCSTask @@ -1616,17 +1616,12 @@ function CONTROLLABLE:TaskFunction( FunctionString, ... ) local ArgumentKey = '_' .. tostring( arg ):match("table: (.*)") self:SetState( self, ArgumentKey, arg ) DCSScript[#DCSScript+1] = "local Arguments = MissionControllable:GetState( MissionControllable, '" .. ArgumentKey .. "' ) " - --DCSScript[#DCSScript+1] = "MissionControllable:ClearState( MissionControllable, '" .. ArgumentKey .. "' ) " DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" else DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" end - DCSTask = self:TaskWrappedAction( - self:CommandDoScript( - table.concat( DCSScript ) - ) - ) + DCSTask = self:TaskWrappedAction(self:CommandDoScript(table.concat( DCSScript ))) self:T( DCSTask ) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 8b7f34a26..2e088d308 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1606,8 +1606,7 @@ end --- Returns true if the first unit of the GROUP is in the air. -- @param Wrapper.Group#GROUP self --- @return #boolean true if in the first unit of the group is in the air. --- @return #nil The GROUP is not existing or not alive. +-- @return #boolean true if in the first unit of the group is in the air or #nil if the GROUP is not existing or not alive. function GROUP:InAir() self:F2( self.GroupName ) From a9a040626ef0cd4126a02132506f06829233a63a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 27 Aug 2018 16:46:46 +0200 Subject: [PATCH 31/73] Warehouse 0.2.7w --- .../Moose/Functional/Warehouse.lua | 390 +++++++++++++++--- 1 file changed, 329 insertions(+), 61 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c9fca1ca5..e07e84a40 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -45,6 +45,8 @@ -- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. -- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. +-- @field Core.Zone#ZONE portzone Zone defining the port of a warehouse. This is where naval assets are spawned. +-- @field #table shippinglanes Table holding the user defined shipping between warehouses. -- @extends Core.Fsm#FSM --- Manages ground assets of an airbase and offers the possibility to transport them to another airbase or warehouse. @@ -71,10 +73,6 @@ -- to another in a realistic and highly automatic fashion. In contrast to a "DCS warehouse" these assets have a physical representation in game. In particular, -- this means they can be destroyed during the transport and add more life to the DCS world. -- --- Or, in other words, on a scale between 1 and 10, how important do you reckon it is to have the right assets at the right place in a conflict? --- --- The warehouse adresses exactly this "problem" by simulating it in a realistic way while at the same time adding more life and dynamics to the DCS World. --- -- This comes along with some additional interesting stategic aspects since capturing/defending and destroying/protecting an enemy or your -- own warehous becomes of critical importance for the development of a conflict. -- @@ -83,23 +81,22 @@ -- -- ## What is a warehouse? -- A warehouse is an abstract object represented by a physical (static) building that can hold virtual assets in stock. --- It can but it must not be associated with a particular airbase. The associated airbase can be an airdrome, a Helipad/FARP or a ship. +-- It can (but it must not) be associated with a particular airbase. The associated airbase can be an airdrome, a Helipad/FARP or a ship. -- -- If another warehouse requests assets, the corresponding troops are spawned at the warehouse and being transported to the requestor or go their -- by themselfs. Once arrived at the requesting warehouse, the assets go into the stock of the requestor and can be activated/deployed when necessary. -- -- ## What assets can be stored? --- Any kind of ground or airborne 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. +-- Any kind of ground, airborne or naval asset can be stored. -- -- ## What means of transportation are available? -- Firstly, all mobile assets can be send from warehouse to another on their own. -- --- * Ground vehicles will use the road infrastructure --- * Airborne units are get a flightplan from the airbase the sending warehouse to the airbase of the receiving warehouse. This already implies that for airborne --- assets both warehouses need an airbase. If either one of the warehouses does not have an associated airbase, transportation of airborne assest is not possible. +-- * Ground vehicles will use the road infrastructure. So a good road connection for both warehouses is important. +-- * Airborne units get a flightplan from the airbase of the sending warehouse to the airbase of the receiving warehouse. This already implies that for airborne +-- assets both warehouses need an airbase. If either one of the warehouses does not have an associated airbase, direct transportation of airborne assest is not possible. -- * Naval units can be exchanged between warehouses which posses a port/habour. Also shipping lanes must be specified manually but the user since DCS does not provide these. --- * Trains use the available railroad infrastructure and both warehouses must have a connection to the railroad. Unfortunately, however, trains are not yet implemented to +-- * Trains (would) use the available railroad infrastructure and both warehouses must have a connection to the railroad. Unfortunately, however, trains are not yet implemented to -- a reasonable degree in DCS at the moment and hence cannot be used yet. -- -- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modelled @@ -111,7 +108,7 @@ -- A MOOSE warehouse must be represented in game by a phyical *static* object. For example, the mission editor already has warehouse as static object available. -- This would be a good first choice but any static object will do. -- --- The positioning of the warehouse static object is very important for a couple of reasons. Firtly, a warehouse needs a good infrastructure so that spawned assets +-- The positioning of the warehouse static object is very important for a couple of reasons. Firstly, a warehouse needs a good infrastructure so that spawned assets -- have a proper road connection or can reach the associated airbase easily. -- -- Once the static warehouse object is placed in the mission editor it can be used as a MOOSE warehouse by the @{#WAREHOUSE.New}(*warehousestatic*, *alias*) constructor, @@ -138,7 +135,8 @@ -- 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. -- -- 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}. +-- Therefore, it is possible, to force a generalized attribute for the asset with the third optional parameter *forceattribute*, which is of type @{#WAREHOUSE.Attribute}. +-- -- -- # Requesting Assets -- @@ -223,7 +221,7 @@ -- 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 --- for further tasking. Here, the groups are only smoked but, of course, you can use them for whatever task you imagine. +-- 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. -- @@ -300,6 +298,8 @@ WAREHOUSE = { queue = {}, pending = {}, defending = {}, + portzone = nil, + shippinglanes = {}, } --- Item of the warehouse stock table. @@ -315,6 +315,7 @@ WAREHOUSE = { -- @field #number speedmax Maximum speed in km/h the unit can do. -- @field DCS#Object.Desc DCSdesc All DCS descriptors. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. +-- @field #boolean istransport If true, the asset is able to transport troops. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -360,7 +361,8 @@ WAREHOUSE.Descriptor = { -- @field #string TRANSPORT_PLANE Airplane with transport capability. Usually bigger, i.e. needs larger airbases and parking spots. -- @field #string TRANSPORT_HELO Helicopter with transport capability. -- @field #string TRANSPORT_APC Amoured Personell Carrier. --- @field #string FIGHER Fighter, interceptor, ... airplane. +-- @field #string TRANSPORT_SHIP Ship defined for troop transport. Since most ships can, this attribute must be manually requestet in the AddAddet() function. +-- @field #string FIGHTER Fighter, interceptor, ... airplane. -- @field #string TANKER Airplane which can refuel other aircraft. -- @field #string AWACS Airborne Early Warning and Control System. -- @field #string ARTILLERY Artillery assets. @@ -368,13 +370,16 @@ WAREHOUSE.Descriptor = { -- @field #string BOMBER Aircraft which can be used for bombing. -- @field #string TANK Tanks. -- @field #string TRUCK Unarmed ground vehicles. --- @field #string TRAIN Trains. --- @field #string SHIP Naval assets. +-- @field #string TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. +-- @field #string AIRCRAFTCARRIER Ship able to carrier aircraft. +-- @field #string WARSHIP Armed war ship, e.g. cruisers, destroyers, firgates and corvettes. +-- @filed #string UNARMED_SHIP Any unarmed naval vessel. -- @field #string OTHER Anything that does not fall into any other category. WAREHOUSE.Attribute = { TRANSPORT_PLANE="Transport_Plane", TRANSPORT_HELO="Transport_Helo", TRANSPORT_APC="Transport_APC", + TRANSPORT_SHIP="Transport_Ship", FIGHTER="Fighter", TANKER="Tanker", AWACS="AWACS", @@ -385,7 +390,9 @@ WAREHOUSE.Attribute = { TANK="Tank", TRUCK="Truck", TRAIN="Train", - SHIP="Ship", + AIRCRAFTCARRIER="Aircraftcarrier", + WAR_SHIP="Warship", + UNARMED_SHIP="Unarmedship", OTHER="Other", } @@ -395,7 +402,7 @@ WAREHOUSE.Attribute = { -- @field #string HELICOPTER Transports are conducted by helicopters. -- @field #string APC Transports are conducted by APCs. -- @field #string SHIP Transports are conducted by ships. --- @field #string TRAIN Transports are conducted by trains. +-- @field #string TRAIN Transports are conducted by trains. Not yet implemented. -- @field #string SELFPROPELLED Assets go to their destination by themselves. No transport carrier needed. WAREHOUSE.TransportType = { AIRPLANE = "Transport_Plane", @@ -417,7 +424,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.7" +WAREHOUSE.version="0.2.7w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -834,6 +841,78 @@ function WAREHOUSE:SetRailConnection(coordinate) return self end +--- Set the port zone for this warehouse. +-- The port zone is the zone, where all naval assets of the warehouse are spawned. +-- @param #WAREHOUSE self +-- @param Core.Zone#ZONE The zone defining the naval port of the warehouse. +-- @return #WAREHOUSE self +function WAREHOUSE:SetPortZone(zone) + self.portzone=zone + return self +end + +--- Add a shipping lane to another warehouse. +-- Note that both warehouses must have a port zone defined before a shipping lane can be added. +-- Shipping lane is taken from the waypoints of a (late activated) template group. So set up a group, e.g. a ship or a helicopter, and place its +-- waypoints along the shipping lane you want to add. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE remotewarehouse The remote warehouse to where the shipping lane is added +-- @param Wrapper.Group#GROUP group Waypoints of this group will define the shipping lane between to warehouses. +-- @return #WAREHOUSE self +function WAREHOUSE:AddShippingLane(remotewarehouse, group) + + -- Check that port zones are defined. + if self.portzone==nil or remotewarehouse.portzone==nil then + self:E(self.wid..string.format("ERROR: Sending or receiving warehouse does not have a port zone defined. Adding shipping lane not possible!")) + return + end + + -- Get route from template. + local lanepoints=group:GetTemplateRoutePoints() + + -- First and last waypoints + local laneF=lanepoints[1] + local laneL=lanepoints[#lanepoints] + + -- Get corresponding coordinates. + local coordF=COORDINATE:New(laneF.x, 0, laneF.y) + local coordL=COORDINATE:New(laneL.x, 0, laneL.y) + + -- Figure out which point is closer to the port of this warehouse. + local distF=self.portzone:GetCoordinate():Get2DDistance(coordF) + local distL=self.portzone:GetCoordinate():Get2DDistance(coordL) + + -- Add the shipping lane. Need to take care of the wrong "direction". + local lane={} + lane.towarehouse=remotewarehouse.warehouse:GetName() + lane.coordinates={} + if distF need airbase for spawning but not for delivering to the spawn zone of the receiver. + if self.category==-1 then + self:E("ERROR: Incorrect request. Warehouse has no airbase. Transport by helicopter not possible!") + valid=false + end + + elseif request.transporttype==WAREHOUSE.TransportType.SHIP then + + -- Transport by ship. + self:E("ERROR: Incorrect request. Transport by SHIP not implemented yet!") + valid=false + + elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then + + -- Transport by train. + self:E("ERROR: Incorrect request. Transport by TRAIN not implemented yet!") + valid=false + + else + -- No match. + self:E("ERROR: Incorrect request. Transport type unknown!") + valid=false + end + + end + + -- Add request as unvalid and delete it later. + if valid==false then + self:E(self.wid..string.format("Got invalid request id=%d.", request.uid)) + else + self:T3(self.wid..string.format("Got valid request id=%d.", request.uid)) + end + + return valid +end + + --- Checks if the request can be fullfilled right now. -- Check for current parking situation, number of assets and transports currently in stock. -- @param #WAREHOUSE self @@ -3285,14 +3533,36 @@ function WAREHOUSE:_CheckQueue() -- Search for a request we can execute. local request=nil --#WAREHOUSE.Queueitem + + local invalid={} + local gotit=false for _,_qitem in ipairs(self.queue) do local qitem=_qitem --#WAREHOUSE.Queueitem + + -- Check if request is valid in general. + local valid=self:_CheckRequestValid(qitem) + + -- Check if request is possible now. local okay=self:_CheckRequestNow(qitem) - if okay==true then + + -- Remember invalid request and delete later in order not to confuse the loop. + if not valid then + table.insert(invalid,request) + end + + -- Get the first valid request that can be executed now. + if okay and valid and not gotit then request=qitem - break + gotit=true + --break end end + + -- Delete invalid requests. + for _,_request in pairs(invalid) do + self:E(self.wid..string.format("Deleting invalid request id=%d.",_request.uid)) + self:_DeleteQueueItem(_request, self.queue) + end -- Execute request. return request @@ -3685,7 +3955,7 @@ function WAREHOUSE:_HasAttribute(groupname, attribute) local group=GROUP:FindByName(groupname) if group then - local groupattribute=self:_HasAttribute(groupname,attribute) + local groupattribute=self:_GetAttribute(groupname) return groupattribute==attribute end @@ -3707,35 +3977,33 @@ function WAREHOUSE:_GetAttribute(groupname) -- Get generalized attributes. -- TODO: need to work on ships and trucks and SAMs and ... -- Also the Yak-52 for example is OTHER since it only has the attribute "Battleplanes". + + -- Airplanes 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") + + -- Helicopters + local transporthelo=group:HasAttribute("Transport helicopters") + local attackhelicopter=group:HasAttribute("Attack helicopters") + + -- Ground + local transportapc=group:HasAttribute("Infantry carriers") + local artillery=group:HasAttribute("Artillery") + local infantry=group:HasAttribute("Infantry") local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") local truck=group:HasAttribute("Trucks") and not group:GetCategory()==Group.Category.TRAIN + + -- Naval + local aircraftcarrier=group:HasAttribute("Aircraft Carriers") + local warship=group:HasAttribute("Armed ships") + local ship=group:HasAttribute("Ships") + + -- Train local train=group:GetCategory()==Group.Category.TRAIN - -- 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 From 8e16fbd000e4a9cb7ff7b74521a73d20aa2d5b92 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 28 Aug 2018 00:34:26 +0200 Subject: [PATCH 32/73] Warehouse v0.2.8 - Added first version of naval assets (self propelled). - Changed WAREHOUSE.Attribute --- .../Moose/Functional/Warehouse.lua | 554 ++++++++---------- 1 file changed, 229 insertions(+), 325 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e07e84a40..f188ff3ea 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -358,42 +358,49 @@ WAREHOUSE.Descriptor = { --- Generalized asset attributes. Can be used to request assets with certain general characteristics. -- @type WAREHOUSE.Attribute --- @field #string TRANSPORT_PLANE Airplane with transport capability. Usually bigger, i.e. needs larger airbases and parking spots. --- @field #string TRANSPORT_HELO Helicopter with transport capability. --- @field #string TRANSPORT_APC Amoured Personell Carrier. --- @field #string TRANSPORT_SHIP Ship defined for troop transport. Since most ships can, this attribute must be manually requestet in the AddAddet() function. --- @field #string FIGHTER Fighter, interceptor, ... airplane. --- @field #string TANKER Airplane which can refuel other aircraft. --- @field #string AWACS Airborne Early Warning and Control System. --- @field #string ARTILLERY Artillery assets. --- @field #string INFANTRY Ground infantry assets. --- @field #string BOMBER Aircraft which can be used for bombing. --- @field #string TANK Tanks. --- @field #string TRUCK Unarmed ground vehicles. --- @field #string TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. --- @field #string AIRCRAFTCARRIER Ship able to carrier aircraft. --- @field #string WARSHIP Armed war ship, e.g. cruisers, destroyers, firgates and corvettes. --- @filed #string UNARMED_SHIP Any unarmed naval vessel. --- @field #string OTHER Anything that does not fall into any other category. +-- @field #string AIR_TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets. +-- @field #string AIR_AWACS Airborne Early Warning and Control System. +-- @field #string AIR_FIGHTER Fighter, interceptor, ... airplane. +-- @field #string AIR_BOMBER Aircraft which can be used for strategic bombing. +-- @field #string AIR_TANKER Airplane which can refuel other aircraft. +-- @field #string AIR_TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets. +-- @field #string AIR_ATTACKHELO Attack helicopter. +-- @field #string AIR_OTHER Any airborne unit that does not fall into any other airborne category. +-- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets. +-- @field #string GROUND_TRUCK Unarmed ground vehicles. +-- @field #string GROUND_INFANTRY Ground infantry assets. +-- @field #string GROUND_ARTILLERY Artillery assets. +-- @field #string GROUND_TANK Tanks (modern or old). +-- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. +-- @field #string GROUND_OTHER Any ground unit that does not fall into any other ground category. +-- @field #string NAVAL_AIRCRAFTCARRIER Aircraft carrier. +-- @field #string NAVAL_WARSHIP War ship, i.e. cruisers, destroyers, firgates and corvettes. +-- @field #string NAVAL_ARMEDSHIP Any armed ship that is not an aircraft carrier, a cruiser, destroyer, firgatte or corvette. +-- @field #string NAVAL_UNARMEDSHIP Any unarmed naval vessel. +-- @field #string NAVAL_OTHER Any naval unit that does not fall into any other naval category. +-- @field #string UNKNOWN Anything that does not fall into any other category. WAREHOUSE.Attribute = { - TRANSPORT_PLANE="Transport_Plane", - TRANSPORT_HELO="Transport_Helo", - TRANSPORT_APC="Transport_APC", - TRANSPORT_SHIP="Transport_Ship", - FIGHTER="Fighter", - TANKER="Tanker", - AWACS="AWACS", - ARTILLERY="Artillery", - ATTACKHELICOPTER="Attackhelicopter", - INFANTRY="Infantry", - BOMBER="Bomber", - TANK="Tank", - TRUCK="Truck", - TRAIN="Train", - AIRCRAFTCARRIER="Aircraftcarrier", - WAR_SHIP="Warship", - UNARMED_SHIP="Unarmedship", - OTHER="Other", + AIR_TRANSPORTPLANE="Air_TransportPlane", + AIR_AWACS="Air_AWACS", + AIR_FIGHTER="Air_Fighter", + AIR_BOMBER="Air_Bomber", + AIR_TANKER="Air_Tanker", + AIR_TRANSPORTHELO="Air_TransportHelo", + AIR_ATTACKHELO="Air_AttackHelo", + AIR_OTHER="Air_Other", + GROUND_APC="Ground_APC", + GROUND_TRUCK="Ground_Truck", + GROUND_INFANTRY="Ground_Infantry", + GROUND_ARTILLERY="Ground_Artillery", + GROUND_TANK="Ground_Tank", + GROUND_TRAIN="Ground_Train", + GROUND_OTHER="Ground_Other", + NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", + NAVAL_WARSHIP="Naval_WarShip", + NAVAL_ARMEDSHIP="Naval_ArmedShip", + NAVAL_UNARMEDSHIP="Naval_UnarmedShip", + NAVAL_OTHER="Naval_Other", + UNKNOWN="Unknown", } --- Cargo transport type. Defines how assets are transported to their destination. @@ -405,11 +412,11 @@ WAREHOUSE.Attribute = { -- @field #string TRAIN Transports are conducted by trains. Not yet implemented. -- @field #string SELFPROPELLED Assets go to their destination by themselves. No transport carrier needed. WAREHOUSE.TransportType = { - AIRPLANE = "Transport_Plane", - HELICOPTER = "Transport_Helo", - APC = "Transport_APC", - SHIP = "Ship", - TRAIN = "Train", + AIRPLANE = "Air_TransportPlane", + HELICOPTER = "Air_TransportHelo", + APC = "Ground_APC", + TRAIN = "Ground_Train", + SHIP = "Naval_UnarmedShip", SELFPROPELLED = "Selfpropelled", } @@ -424,7 +431,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.7w" +WAREHOUSE.version="0.2.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -844,7 +851,7 @@ end --- Set the port zone for this warehouse. -- The port zone is the zone, where all naval assets of the warehouse are spawned. -- @param #WAREHOUSE self --- @param Core.Zone#ZONE The zone defining the naval port of the warehouse. +-- @param Core.Zone#ZONE zone The zone defining the naval port of the warehouse. -- @return #WAREHOUSE self function WAREHOUSE:SetPortZone(zone) self.portzone=zone @@ -884,31 +891,32 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group) -- Add the shipping lane. Need to take care of the wrong "direction". local lane={} - lane.towarehouse=remotewarehouse.warehouse:GetName() - lane.coordinates={} + --lane.towarehouse=remotewarehouse.warehouse:GetName() + --lane.coordinates={} if distF need airbase for spawning but not for delivering to the spawn zone of the receiver. - if self.category==-1 then - self:E("ERROR: Incorrect request. Warehouse has no airbase. Transport by helicopter not possible!") - valid=false - end - - elseif request.transporttype==WAREHOUSE.TransportType.SHIP then - - -- Transport by ship. - self:E("ERROR: Incorrect request. Transport by SHIP not implemented yet!") - valid=false - - elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then - - -- Transport by train. - self:E("ERROR: Incorrect request. Transport by TRAIN not implemented yet!") - valid=false - - else - -- No match. - self:E("ERROR: Incorrect request. Transport type unknown!") - valid=false - end - - end - - -- Add request as unvalid and delete it later. - if valid==false then - self:E(self.wid..string.format("Got invalid request id=%d.", request.uid)) - table.insert(invalid, request) - else - self:T3(self.wid..string.format("Got valid request id=%d.", request.uid)) - end - - end -- loop queue items. - + end end --- Check if a request is valid in general. If not, it will be removed from the queue. @@ -3339,13 +3215,16 @@ function WAREHOUSE:_CheckRequestValid(request) end if asset_train then + -- Check if there is a valid path on rail. local hasrail=self:HasConnectionRail(request.warehouse) if not hasrail then self:E("ERROR: Incorrect request. No valid path on rail for train assets!") valid=false end + else + if self.warehouse:GetName()~=request.warehouse.warehouse:GetName() then -- Check if there is a valid path on road. local hasroad=self:HasConnectionRoad(request.warehouse) @@ -3354,11 +3233,17 @@ function WAREHOUSE:_CheckRequestValid(request) valid=false end end - end + + end + elseif asset_naval then - self:E("ERROR: Incorrect request. Naval units not supported yet!") - valid=false + local shippinglane=self:HasConnectionNaval(request.warehouse) + + if not shippinglane then + self:E("ERROR: Incorrect request. No shipping lane has been defined between warehouses!") + valid=false + end end @@ -3597,14 +3482,17 @@ end --@return Wrapper.Airbase#AIRBASE.TerminalType Terminal type for this group. function WAREHOUSE:_GetTerminal(_attribute) + -- Default terminal is "large". local _terminal=AIRBASE.TerminalType.OpenBig - if _attribute==WAREHOUSE.Attribute.FIGHTER then + + + if _attribute==WAREHOUSE.Attribute.AIR_FIGHTER then -- Fighter ==> small. _terminal=AIRBASE.TerminalType.FighterAircraft - elseif _attribute==WAREHOUSE.Attribute.BOMBER or _attribute==WAREHOUSE.Attribute.TRANSPORT_PLANE or _attribute==WAREHOUSE.Attribute.TANKER or _attribute==WAREHOUSE.Attribute.AWACS then + elseif _attribute==WAREHOUSE.Attribute.AIR_BOMBER or _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTPLANE or _attribute==WAREHOUSE.Attribute.AIR_TANKER or _attribute==WAREHOUSE.Attribute.AIR_AWACS then -- Bigger aircraft. _terminal=AIRBASE.TerminalType.OpenBig - elseif _attribute==WAREHOUSE.Attribute.TRANSPORT_HELO or _attribute==WAREHOUSE.Attribute.ATTACKHELICOPTER then + elseif _attribute==WAREHOUSE.Attribute.AIR_TRANSPORTHELO or _attribute==WAREHOUSE.Attribute.AIR_ATTACKHELO then -- Helicopter. _terminal=AIRBASE.TerminalType.HelicopterUsable end @@ -3963,6 +3851,7 @@ function WAREHOUSE:_HasAttribute(groupname, attribute) end --- Get the generalized attribute of a group. +-- Note that for a heterogenious group, the attribute is determined from the attribute of the first unit! -- @param #WAREHOUSE self -- @param #string groupname Name of the group. -- @return #WAREHOUSE.Attribute Generalized attribute of the group. @@ -3978,61 +3867,76 @@ function WAREHOUSE:_GetAttribute(groupname) -- TODO: need to work on ships and trucks and SAMs and ... -- Also the Yak-52 for example is OTHER since it only has the attribute "Battleplanes". - -- Airplanes + ----------- + --- Air --- + ----------- + -- Planes local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") - 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 fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") local bomber=group:HasAttribute("Bombers") - + local tanker=group:HasAttribute("Tankers") -- Helicopters local transporthelo=group:HasAttribute("Transport helicopters") local attackhelicopter=group:HasAttribute("Attack helicopters") - + + -------------- + --- Ground --- + -------------- -- Ground - local transportapc=group:HasAttribute("Infantry carriers") - local artillery=group:HasAttribute("Artillery") - local infantry=group:HasAttribute("Infantry") - local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") + local apc=group:HasAttribute("Infantry carriers") local truck=group:HasAttribute("Trucks") and not group:GetCategory()==Group.Category.TRAIN - - -- Naval - local aircraftcarrier=group:HasAttribute("Aircraft Carriers") - local warship=group:HasAttribute("Armed ships") - local ship=group:HasAttribute("Ships") - + local infantry=group:HasAttribute("Infantry") + local artillery=group:HasAttribute("Artillery") + local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") -- Train local train=group:GetCategory()==Group.Category.TRAIN + ------------- + --- Naval --- + ------------- + -- Ships + local aircraftcarrier=group:HasAttribute("Aircraft Carriers") + local warship=group:HasAttribute("Heavy armed ships") + local armedship=group:HasAttribute("Armed ships") + local unarmedship=group:HasAttribute("Unarmed ships") + + -- Define attribute. Order is important. 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 + attribute=WAREHOUSE.Attribute.AIR_TRANSPORTPLANE elseif awacs then - attribute=WAREHOUSE.Attribute.AWACS + attribute=WAREHOUSE.Attribute.AIR_AWACS + elseif fighter then + attribute=WAREHOUSE.Attribute.AIR_FIGHTER elseif bomber then - attribute=WAREHOUSE.Attribute.BOMBER - elseif artillery then - attribute=WAREHOUSE.Attribute.ARTILLERY - elseif infantry then - attribute=WAREHOUSE.Attribute.INFANTRY - elseif attackhelicopter then - attribute=WAREHOUSE.Attribute.ATTACKHELICOPTER - elseif tank then - attribute=WAREHOUSE.Attribute.TANK + attribute=WAREHOUSE.Attribute.AIR_BOMBER + elseif tanker then + attribute=WAREHOUSE.Attribute.AIR_TANKER + elseif transporthelo then + attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO + elseif apc then + attribute=WAREHOUSE.Attribute.GROUND_APC elseif truck then - attribute=WAREHOUSE.Attribute.TRUCK + attribute=WAREHOUSE.Attribute.GROUND_TRUCK + elseif infantry then + attribute=WAREHOUSE.Attribute.GROUND_INFANTRY + elseif artillery then + attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY + elseif tank then + attribute=WAREHOUSE.Attribute.GROUND_TANK elseif train then - attribute=WAREHOUSE.Attribute.TRAIN + attribute=WAREHOUSE.Attribute.GROUND_TRAIN + elseif aircraftcarrier then + attribute=WAREHOUSE.Attribute.NAVAL_AIRCRAFTCARRIER + elseif warship then + attribute=WAREHOUSE.Attribute.NAVAL_WARSHIP + elseif armedship then + attribute=WAREHOUSE.Attribute.NAVAL_ARMEDSHIP + elseif unarmedship then + attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP else - attribute=WAREHOUSE.Attribute.OTHER + attribute=WAREHOUSE.Attribute.UNKNOWN end end From 2112915dd26ae2a2749434c1806769c092b13830 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 28 Aug 2018 16:29:03 +0200 Subject: [PATCH 33/73] Warehouse v0.2.8w --- .../Moose/Functional/Warehouse.lua | 189 ++++++++++++++---- .../Moose/Wrapper/Positionable.lua | 3 +- 2 files changed, 153 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index f188ff3ea..aad2c6c7e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3,14 +3,11 @@ -- -- Features: -- --- * Holds (virtual) assests such as intrantry groups in stock. +-- * Holds (virtual) assests in stock. -- * Manages requests of assets from other warehouses. --- * Take care of transportation to other warehouses and its accociated airbases. +-- * Realistic transportation of assets between warehouses. -- * Different means of automatic transportation (planes, helicopters, APCs, selfpropelled). --- --- # QUICK START GUIDE --- --- **WIP** +-- * Strategic components such as capturing, defending and destroying warehouses and their associated infrastructure. -- -- === -- @@ -65,7 +62,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.JPG) +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.jpg) -- -- # The Warehouse Concept -- @@ -108,6 +105,8 @@ -- A MOOSE warehouse must be represented in game by a phyical *static* object. For example, the mission editor already has warehouse as static object available. -- This would be a good first choice but any static object will do. -- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Static.png) +-- -- The positioning of the warehouse static object is very important for a couple of reasons. Firstly, a warehouse needs a good infrastructure so that spawned assets -- have a proper road connection or can reach the associated airbase easily. -- @@ -156,7 +155,7 @@ -- -- So for example: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.INFANTRY, 5, WAREHOUSE.TransportType.APC, 2, 20) +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2, 20) -- -- Here, warehouse Kobuleti requests 5 infantry groups from warehouse Batumi. These "cargo" assets should be transported from Batumi to Kobuleti by 2 APCS. -- Note that the warehouse at Batumi needs to have at least five infantry groups and two APC groups in their stock if the request can be processed. @@ -195,7 +194,7 @@ -- Assets in the warehouse' stock can used for user defined tasks realtively easily. They can be spawned into the game by a "self request", i.e. the warehouse -- requests the assets from itself: -- --- warehouseBatumi:AddRequest(warehouseBatumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.INFANTRY, 5) +-- warehouseBatumi:AddRequest(warehouseBatumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5) -- -- This would simply spawn five infantry groups in the spawn zone of the Batumi warehouse if/when they are available. -- @@ -225,6 +224,62 @@ -- -- Note that airborne groups are spawned in uncontrolled state and need to be activated first before they can start their assigned mission. -- +-- # Infrastructure +-- +-- A good infrastructure is important for a warehouse to be efficient. Therefore, the location of a warehouse should be chosen with care. +-- This can also help to avoid many DCS related issues such as units getting stuck in buildings, blocking taxi ways etc. +-- +-- ## Spawn Zone +-- +-- By default, the zone were ground assets are spawned is a circular zone around the physical location of the warehouse with a radius of 200 meters. However, the location of the +-- spawn zone can be set by the @{#WAREHOUSE.SetSpawnZone}(*zone*) functions. It is advisable to choose a zone which is clear of obstacles. +-- +-- The parameter *zone* is a MOOSE @{Core.Zone#ZONE} object. So one can, e.g., use trigger zones defined in the mission editor. If a cicular zone is not desired, one +-- can use a polygon zone (see @{Core.Zone#ZONE_POLYGON}). +-- +-- ## Road Connections +-- +-- Ground assets will use a road connection to travel from one warehouse to another. Therefore, a proper road connection is necessary. +-- +-- By default, the closest point on road to the center of the spawn zone is choses as road connection automatically. But only, if distance between the spawn zone +-- and the road connection is less than 3 km. +-- +-- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. +-- +-- ## Rail Connections +-- +-- A rail connection is automatically defined as the closest point on a railway measured from the center of the spawn zone. But only, if the distance is less than 3 km. +-- +-- The mission designer can manually specify a rail connection with the @{#WAREHOUSE.SetRailConnection} function. +-- +-- **NOTE** however, that trains in DCS are currently not implemented in a way so that they can be used. +-- +-- ## Air Connections +-- +-- In order to use airborne assets, a warehouse needs to have an associated airbase. This can be an airdrome or a FARP/HELOPAD. +-- +-- If there is an airbase within 3 km range of the warehouse it is automatically set as the associated airbase. A user can set an airbase manually +-- with the @{#WAREHOUSE.SetAirbase} function. Keep in mind, that sometimes, ground units need to walk/drive from the spawn zone to the airport +-- to get to their transport carriers. +-- +-- ## Naval Connections +-- +-- Natively, DCS does not have the concept of a port/habour or shipping lanes. So in order to have a meaningful transfer of naval units between warehouses, these have to be +-- defined by the mission designer. +-- +-- ### Defining a Port +-- +-- A port in this context is the zone where all naval assets are spawned. This zone can be defined with the function @{#WAREHOUSE.SetPortZone}(*zone*), where the parameter +-- *zone* is a MOOSE zone. So again, this can be create from a trigger zone defined in the mission editor or if a general shape is desired by a @{Core.Zone#ZONE_POLYGON}. +-- +-- ### Defining Shipping Lanes +-- +-- A shipping lane between to warehouses can be defined by the @{#WAREHOUSE.AddShippingLane}(*remotewarehouse*, *group*) function. The first parameter *remotewarehouse* +-- is the warehouse which should be connected to the present warehouse. +-- +-- The parameter *group* should be a late activated group defined in the mission editor. The waypoints of this group are used as waypoints of the shipping lane. +-- +-- -- # Strategic Considerations -- -- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a (potentially) juicy target for enemy attacks. @@ -311,11 +366,13 @@ WAREHOUSE = { -- @field #string unittype Type of the first unit of the group as obtained by the Object.getTypeName() DCS API function. -- @field #number nunits Number of units in the group. -- @field #number range Range of the unit in meters. +-- @field #number speedmax Maximum speed in km/h the group can do. -- @field #number size Maximum size in length and with of the asset in meters. --- @field #number speedmax Maximum speed in km/h the unit can do. +-- @field #number weight The weight of the whole asset group in kilo gramms. -- @field DCS#Object.Desc DCSdesc All DCS descriptors. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. --- @field #boolean istransport If true, the asset is able to transport troops. +-- @field #boolean transporter If true, the asset is able to transport troops. +-- @field #number cargobay Weight in kg that fits in the cargo bay of one asset unit. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -431,15 +488,17 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.8" +WAREHOUSE.version="0.2.8w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Take cargo weight into consideration, when selecting -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. --- TODO: Add harbours and ports. --- TODO: Add shipping lanes between warehouses. +-- DONE: Add ports for spawning naval assets. +-- TODO: Added habours as interface for transport to from warehouses? +-- DONE: Add shipping lanes between warehouses. -- TODO: Set ROE for spawned groups. -- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. -- TODO: Write documentation. @@ -450,7 +509,7 @@ WAREHOUSE.version="0.2.8" -- TODO: Add general message function for sending to coaliton or debug. -- TODO: Fine tune event handlers. -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. --- TODO: Improve generalized attributes. +-- DONE: Improve generalized attributes. -- TODO: Add a time stamp when an asset is added to the stock and for requests -- DONE: If warehouse is destoyed, all asssets are gone. -- DONE: Add event handlers. @@ -1315,6 +1374,20 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() local smax,sx,sy,sz=_GetObjectSize(DCSdesc) + + -- Get weight in kg + local weight=0 + local DCSunits=DCSgroup:getUnits() + for _,unit in pairs(DCSunits) do + local desc=unit:getDesc() + local unitweight=desc.emptyWeight + weight=weight+unitweight + end + + for _,_unit in pairs(group:GetUnits()) do + local unit=_unit --Wrapper.Unit#UNIT + unit:GetCargoBayFreeWeight() + end -- Set/get the generalized attribute. local attribute=forceattribute or self:_GetAttribute(templategroupname) @@ -1335,12 +1408,14 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) asset.category=DCScategory asset.unittype=DCStype - asset.nunits=#asset.template.units - asset.attribute=attribute + asset.nunits=#asset.template.units asset.range=RangeMin asset.speedmax=SpeedMax - asset.size=smax + asset.size=smax + asset.weight=weight asset.DCSdesc=DCSdesc + asset.attribute=attribute + asset.transporter=false -- not used yet if i==1 then self:_AssetItemInfo(asset) @@ -1367,9 +1442,11 @@ function WAREHOUSE:_AssetItemInfo(asset) text=text..string.format("Attribute = %s\n", asset.attribute) text=text..string.format("Category = %d\n", asset.category) text=text..string.format("Units # = %d\n", asset.nunits) - text=text..string.format("Size max = %5.2f m\n", asset.size) text=text..string.format("Speed max = %5.2f km/h\n", asset.speedmax) text=text..string.format("Range max = %5.2f km\n", asset.range/1000) + text=text..string.format("Size max = %5.2f m\n", asset.size) + text=text..string.format("Weight total = %5.2f kg\n", asset.weight) + text=text..string.format("Cargo bay = %5.2f kg\n", asset.cargobay) self:E(self.wid..text) self:E({DCSdesc=asset.DCSdesc}) self:E({Template=asset.template}) @@ -1701,7 +1778,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Check if destination is in range. if asset.range0 then -- Get the attibute of the requested asset. @@ -3859,7 +3972,7 @@ function WAREHOUSE:_GetAttribute(groupname) local group=GROUP:FindByName(groupname) - local attribute=WAREHOUSE.Attribute.OTHER --#WAREHOUSE.Attribute + local attribute=WAREHOUSE.Attribute.UNKNOWN --#WAREHOUSE.Attribute if group then diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index ac10a4f90..906fd31d5 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -941,7 +941,8 @@ do -- Cargo -- end -- return self.__.CargoBayVolumeLimit - CargoVolume -- end --- +-- + --- Get Cargo Bay Free Weight in kg. -- @param #POSITIONABLE self -- @return #number CargoBayFreeWeight From a8d96d332f9eafc071e18845ac5e8d1a8310cdb8 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 29 Aug 2018 00:16:57 +0200 Subject: [PATCH 34/73] Warehouse v0.2.9 not working, need to find a way for transports --- .../Moose/Functional/Warehouse.lua | 189 ++++++++++++++++-- .../Moose/Wrapper/Positionable.lua | 2 +- 2 files changed, 175 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index aad2c6c7e..41c709762 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -139,7 +139,7 @@ -- -- # Requesting Assets -- --- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinize to check if can be fullfilled at all. If the request is valid, it is +-- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinize to check if can be fulfilled at all. If the request is valid, it is -- put into the warehouse queue and processed as soon as possible. -- -- A request can be assed by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*) function. @@ -488,7 +488,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.8w" +WAREHOUSE.version="0.2.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1376,17 +1376,19 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) local smax,sx,sy,sz=_GetObjectSize(DCSdesc) -- Get weight in kg + env.info("FF get weight") local weight=0 - local DCSunits=DCSgroup:getUnits() - for _,unit in pairs(DCSunits) do - local desc=unit:getDesc() - local unitweight=desc.emptyWeight - weight=weight+unitweight - end - + local cargobay=0 for _,_unit in pairs(group:GetUnits()) do local unit=_unit --Wrapper.Unit#UNIT - unit:GetCargoBayFreeWeight() + local Desc=unit:GetDesc() + self:E({UnitDesc=Desc}) + local unitweight=Desc.massEmpty + if unitweight then + weight=weight+unitweight + env.info("FF weight = "..weight) + end + cargobay=unit:GetCargoBayFreeWeight() end -- Set/get the generalized attribute. @@ -1416,6 +1418,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) asset.DCSdesc=DCSdesc asset.attribute=attribute asset.transporter=false -- not used yet + asset.cargobay=cargobay if i==1 then self:_AssetItemInfo(asset) @@ -1696,6 +1699,87 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- On before "AddRequest" event. Checks some basic properties of the given parameters. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #WAREHOUSE warehouse The warehouse 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. +-- @param #string Assignment A keyword or text that +-- @return #boolean If true, request is okay at first glance. +function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Assignment, Prio) + + -- Defaults. + nAsset=nAsset or 1 + TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED + Prio=Prio or 50 + if nTransport==nil then + if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then + nTransport=0 + else + nTransport=1 + end + end + + -- Request is okay. + local okay=true + + if AssetDescriptor==WAREHOUSE.Descriptor.ATTRIBUTE then + + -- Check if a valid attibute was given. + local gotit=false + for _,attribute in pairs(WAREHOUSE.Attribute) do + if AssetDescriptorValue==attribute then + gotit=true + end + end + if not gotit then + self:E(self.wid.."ERROR: Invalid request. Asset attribute is unknown!") + okay=false + end + + elseif AssetDescriptor==WAREHOUSE.Descriptor.CATEGORY then + + -- Check if a valid category was given. + local gotit=false + for _,category in pairs(Group.Category) do + if AssetDescriptorValue==category then + gotit=true + end + end + if not gotit then + self:E(self.wid.."ERROR: Invalid request. Asset category is unknown!") + okay=false + end + + elseif AssetDescriptor==WAREHOUSE.Descriptor.TEMPLATENAME then + + if type(AssetDescriptorValue)~="string" then + self:E(self.wid.."ERROR: Invalid request. Asset template name must be passed as a string!") + okay=false + end + + elseif AssetDescriptor==WAREHOUSE.Descriptor.UNITTYPE then + + if type(AssetDescriptorValue)~="string" then + self:E(self.wid.."ERROR: Invalid request. Asset unit type must be passed as a string!") + okay=false + end + + else + self:E(self.wid.."ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, TEMPLATENAME or UNITTYPE!") + okay=false + end + + return okay +end + --- On after "AddRequest" event. Add a request to the warehouse queue, which is processed when possible. -- @param #WAREHOUSE self -- @param #string From From state. @@ -1749,7 +1833,7 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- On before "Request" event. Checks if the request can be fullfilled. +--- On before "Request" event. Checks if the request can be fulfilled. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -2076,7 +2160,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _alias=self:_Alias(_assetitem, Request) -- Spawn ground asset. - local spawngroup=self:_SpawnAssetGround(_assetitem, Request, self.spawnzone) + local spawngroup=self:_SpawnAssetGroundNaval(_assetitem, Request, self.spawnzone) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -3431,10 +3515,10 @@ function WAREHOUSE:_CheckRequestValid(request) end ---- Checks if the request can be fullfilled right now. +--- Checks if the request can be fulfilled right now. -- Check for current parking situation, number of assets and transports currently in stock. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Queueitem request The request to be checked. +-- @param #WAREHOUSE.Pendingitem request The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. function WAREHOUSE:_CheckRequestNow(request) @@ -3478,10 +3562,22 @@ function WAREHOUSE:_CheckRequestNow(request) end end + request.assets=_assets + --request. + end -- Check that a transport units. if request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then + + + + + _transports=self:_GetTransportsForAssets(request) + + + + elseif false then -- Transports in stock. local _transports,_ntransports,_enough=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) @@ -3521,7 +3617,70 @@ function WAREHOUSE:_CheckRequestNow(request) return okay end ----Sorts the queue and checks if the request can be fullfilled. +---Get (optimized) transport carriers for the given assets to be transported. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Pendingitem Chosen request. +function WAREHOUSE:_GetTransportsForAssets(request) + + -- Get all transports of the requested type in stock. + local transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype) + + local cargoassets=request.assets + + -- Problems/questions + -- 1. Do we have at least one carrier big enough to transport the largest group? + -- If not ==> No transport possible since groups cannot be split! + -- If yes ==> Tranport possible. + -- 2. How many carriers do we need? + -- ntransport should be the max number. + + -- Example 8, 8, 5, 3 + -- Carriers: + -- 2 that can take 8 can be used for 3, 5, 8 + -- 1 that can take 6 can be used for 3, 5, - + -- 3 that can take 4 can be used for 3, -, - + -- 1 that can take 2 can be used for -, -, - + + -- So the problem becomes: + -- How do I minimize the number of "ways" with the constraint of a fixed number of carriers? + -- Extreme cases: + -- Use just one carrier that can carrier the largest group. I would have to drive n times to get all cargo from A to B. + -- + + -- The most simple way is to sort the transports in descending order wrt. to their cargo bay size. + -- Use largest carriers available until either number of cargo is done in one run or we hit max number of carriers available. + + -- sort transport carriers w.r.t. cargo bay size. + local function sort_transports(a,b) + return a.cargobay>b.cargobay + end + + -- sort cargo assets w.r.t. weight in assending order + local function sort_cargoassets(a,b) + return a.weight>b.weight + end + + table.sort(transports, sort_transports) + table.sort(cargoassets, sort_cargoassets) + + -- Very simple! Only take the largest transports that can carrier the largest cargo. + local used_transports={} + local maxcargoweight=cargoassets[1] + for i=1,#transports do + local transport=transports[i] --#WAREHOUSE.Assetitem + if transport.cargobay>maxcargoweight and #used_transports<=request.ntransport then + table.insert(used_transports, transport) + end + end + + for _,_transport in ipairs(used_transports) do + local transport=_transport --#WAREHOUSE.Assetitem + --env.info("transport used = ", transport.) + end + +end + +---Sorts the queue and checks if the request can be fulfilled. -- @param #WAREHOUSE self -- @return #WAREHOUSE.Queueitem Chosen request. function WAREHOUSE:_CheckQueue() diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 984ebfd65..66e5df18b 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1010,7 +1010,7 @@ do -- Cargo ["TPZ"] = 10, } - local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 70 + local CargoBayWeightLimit = ( Weights[Desc.typeName] or 0 ) * 90 self.__.CargoBayWeightLimit = CargoBayWeightLimit end end From ae04196584fc693b81f3c0fb217b897aa67ad376 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 29 Aug 2018 15:49:41 +0200 Subject: [PATCH 35/73] Warehouse v0.2.9w --- Moose Development/Moose/Functional/Warehouse.lua | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 41c709762..e86eea55b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -488,7 +488,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.9" +WAREHOUSE.version="0.2.9w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -3562,21 +3562,19 @@ function WAREHOUSE:_CheckRequestNow(request) end end + -- Set chosen assets. request.assets=_assets - --request. + request.cargoattribute=_assetattribute + request.cargocategory=_assetcategory end -- Check that a transport units. if request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then - + local _transports=self:_GetTransportsForAssets(request) - _transports=self:_GetTransportsForAssets(request) - - - elseif false then -- Transports in stock. From 6fbf584e812c247f47f3be30aa5703d305268172 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 29 Aug 2018 23:33:13 +0200 Subject: [PATCH 36/73] Warehouse v0.3.0 --- .../Moose/Functional/Warehouse.lua | 196 ++++++++++++------ 1 file changed, 128 insertions(+), 68 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e86eea55b..a56574a36 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1,5 +1,8 @@ --- **Functional** - (R2.5) - Simulation of logistics. --- +-- +-- 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 +-- force without resources and transportation is defenseless. -- -- Features: -- @@ -8,6 +11,7 @@ -- * Realistic transportation of assets between warehouses. -- * Different means of automatic transportation (planes, helicopters, APCs, selfpropelled). -- * Strategic components such as capturing, defending and destroying warehouses and their associated infrastructure. +-- * Can be coupled to other MOOSE classes. -- -- === -- @@ -46,7 +50,7 @@ -- @field #table shippinglanes Table holding the user defined shipping between warehouses. -- @extends Core.Fsm#FSM ---- Manages ground assets of an airbase and offers the possibility to transport them to another airbase or warehouse. +--- Have your assets at the right place at the right time - or not! -- -- === -- @@ -381,24 +385,27 @@ WAREHOUSE = { -- @field #WAREHOUSE warehouse Requesting warehouse. -- @field Wrapper.Airbase#AIRBASE airbase Requesting airbase or airbase beloning to requesting warehouse. -- @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 #WAREHOUSE.Descriptor assetdesc Descriptor of the requested asset. Enumerator of type @{#WAREHOUSE.Descriptor}. +-- @field assetdescval Value of the asset descriptor. Type depends on "assetdesc" descriptor. -- @field #number nasset Number of asset groups requested. -- @field #WAREHOUSE.TransportType transporttype Transport unit type. --- @field #number ntransport Number of transport units requested. +-- @field #number ntransport Max. number of transport units requested. +-- @field #boolean toself Self request, i.e. warehouse requests assets from itself. +-- @field #table assets Table of self propelled (or cargo) and transport assets. Each element of the table is a @{#WAREHOUSE.Assetitem} and can be accessed by their asset ID. +-- @field #table cargoassets Table of cargo (or self propelled) assets. Each element of the table is a @{#WAREHOUSE.Assetitem}. +-- @field #number cargoattribute Attribute of cargo assets of type @{#WAREHOUSE.Attribute}. +-- @field #number cargocategory Category of cargo assets of type @{#WAREHOUSE.Category}. +-- @field #table transportassets Table of transport carrier assets. Each element of the table is a @{#WAREHOUSE.Assetitem}. +-- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}. +-- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem -- @extends #WAREHOUSE.Queueitem --- @field #table assets Table of assets - cargo or transport. Each element of the table is a @{#WAREHOUSE.Assetitem}. -- @field Core.Set#SET_GROUP cargogroupset Set of cargo groups do be delivered. -- @field #number ndelivered Number of groups delivered to destination. --- @field #number cargoattribute Attribute of cargo assets of type @{#WAREHOUSE.Attribute}. --- @field #number cargocategory Category of cargo assets of type @{#WAREHOUSE.Category}. -- @field Core.Set#SET_GROUP transportgroupset Set of cargo transport groups. --- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}. --- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. --- @field #number ntransporthome Number of transports back home. transportattribute +-- @field #number ntransporthome Number of transports back home. --- Descriptors enumerator describing the type of the asset. -- @type WAREHOUSE.Descriptor @@ -488,13 +495,15 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.2.9w" +WAREHOUSE.version="0.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Take cargo weight into consideration, when selecting +-- TODO: Warehouse re-capturing not working?! +-- TODO: Naval assets dont go back into stock once arrived. +-- TODO: Take cargo weight into consideration, when selecting transport assets. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- DONE: Add ports for spawning naval assets. -- TODO: Added habours as interface for transport to from warehouses? @@ -1846,8 +1855,8 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Distance from warehouse to requesting warehouse. local distance=self.coordinate:Get2DDistance(Request.warehouse.coordinate) - -- Filter the requested assets. - local _assets,_nasset,_enough=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) + -- Shortcut to cargoassets. + local _assets=Request.cargoassets if Request.nasset==0 then local text=string.format("Request denied! Zero assets were requested.") @@ -1875,12 +1884,14 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) end - -- Asset is not in stock ==> request denied. - if not _enough then - local text=string.format("Request denied! Not enough assets currently in stock. Requested %s < %d in stock.", tostring(Request.nasset), _nasset) - MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - return false + -- Init asset table. + Request.assets={} + + -- Init self request. + if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then + Request.toself=true + else + Request.toself=false end return true @@ -1901,33 +1912,31 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Pending request. Add cargo groups to request. local Pending=Request --#WAREHOUSE.Pendingitem - Pending.assets={} -- Spawn assets of this request. - local _spawngroups,_cargoassets=self:_SpawnAssetRequest(Pending) --Core.Set#SET_GROUP + local _spawngroups=self:_SpawnAssetRequest(Pending) --Core.Set#SET_GROUP -- Check if any group was spawned. If not, delete the request. if _spawngroups:Count()==0 then - -- Delete request from queue. - self:_DeleteQueueItem(Request, self.queue) self:E(self.wid..string.format("ERROR: Groups or request %d could not be spawned. Request is rejected and deleted from queue!", Request.uid)) + -- Delete request from queue. + self:_DeleteQueueItem(Request, self.queue) return end -- General type and category. - local _cargotype=_cargoassets[1].attribute --#WAREHOUSE.Attribute - local _cargocategory=_cargoassets[1].category --DCS#Group.Category + local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute + local _cargocategory=Request.cargocategory --DCS#Group.Category + -- Add groups to pending item. Pending.cargogroupset=_spawngroups - Pending.cargoattribute=_cargotype - Pending.cargocategory=_cargocategory ------------------------------------------------------------------------------------------------------------------------------------ -- Self request: assets are spawned at warehouse but not transported anywhere. ------------------------------------------------------------------------------------------------------------------------------------ -- Self request! Assets are only spawned but not routed or transported anywhere. - if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then + if Request.toself then self:E(self.wid..string.format("Selfrequest! Current status %s", self:GetState())) -- Add request to pending queue. @@ -2011,7 +2020,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --TODO: make nearradius depended on transport type and asset type. local _loadradius=5000 - local _nearradius=35 + local _nearradius=nil if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then _loadradius=5000 @@ -2048,12 +2057,12 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Cargo dispatcher. 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, Request.ntransport) + -- Shortcut to transport assets. + local _assetstock=Request.transportassets -- General type and category. - local _transporttype=_assetstock[1].attribute --#WAREHOUSE.Attribute - local _transportcategory=_assetstock[1].category --DCS#Group.Category + local _transporttype=Request.transportattribute + local _transportcategory=Request.transportcategory -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} @@ -2251,8 +2260,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add cargo groups to request. Pending.transportgroupset=TransportSet - Pending.transportattribute=_transporttype - Pending.transportcategory=_transportcategory -- Add request to pending queue. table.insert(self.pending, Pending) @@ -2267,34 +2274,26 @@ end -- @param #WAREHOUSE self -- @param #WAREHOUSE.Queueitem Request Information table of the request. -- @return Core.Set#SET_GROUP Set of groups that were spawned. --- @return #table List of spawned assets. function WAREHOUSE:_SpawnAssetRequest(Request) self:E({requestUID=Request.uid}) - -- Filter the requested cargo assets. - local _assetstock,_nasset,_enough=self:_FilterStock(self.stock, Request.assetdesc, Request.assetdescval, Request.nasset) - - self:E({num_assetstoc=#_assetstock, nasset=_nasset, enough=_enough}) - - -- No assets in stock :( - if not _enough then - return nil,nil,nil - end + -- Shortcut to cargo assets. + local _assetstock=Request.cargoassets -- General type and category. - local _cargotype=_assetstock[1].attribute --#WAREHOUSE.Attribute - local _cargocategory=_assetstock[1].category --DCS#Group.Category + local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute + local _cargocategory=Request.cargocategory --DCS#Group.Category -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_FindParkingForAssets(self.airbase,_assetstock) + Parking=self:_FindParkingForAssets(self.airbase,_assetstock) end -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. local UnControlled=false local AIOnOff=true - if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then + if Request.toself then UnControlled=true AIOnOff=false end @@ -2363,8 +2362,11 @@ function WAREHOUSE:_SpawnAssetRequest(Request) Request.assets[asset.uid]=asset self:_DeleteStockItem(asset) end + + -- Overwrite the assets with the actually spawned ones. + Request.cargoassets=_assets - return _groupset,_assets + return _groupset end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2795,7 +2797,8 @@ function WAREHOUSE:_RouteNaval(group, request) end -- Task function triggering the arrived event at the last waypoint. - local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) + --local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) + local TaskFunction = self:_SimpleTaskFunction("WAREHOUSE:_ArrivedSimple", group) -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] @@ -2869,7 +2872,7 @@ end --- Task function for last waypoint. Triggering the "Arrived" event. -- @param Wrapper.Group#GROUP group The group that arrived. --- @param #WAREHOUSE self +-- @param #WAREHOUSE warehouse Warehouse self. function WAREHOUSE._Arrived(group, warehouse) env.info(warehouse.wid..string.format("Group %s arrived at destination.", tostring(group:GetName()))) @@ -3530,7 +3533,8 @@ function WAREHOUSE:_CheckRequestNow(request) local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.", self.alias, request.warehouse.alias, request.warehouse:GetState()) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) - okay=false + + return false end -- Check if number of requested assets is in stock. @@ -3541,7 +3545,8 @@ function WAREHOUSE:_CheckRequestNow(request) local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) - okay=false + + return false end -- Check if at least one (cargo) asset is available. @@ -3558,12 +3563,13 @@ function WAREHOUSE:_CheckRequestNow(request) local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all assets at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) - okay=false + + return false end end -- Set chosen assets. - request.assets=_assets + request.cargoassets=_assets request.cargoattribute=_assetattribute request.cargocategory=_assetcategory @@ -3575,6 +3581,51 @@ function WAREHOUSE:_CheckRequestNow(request) local _transports=self:_GetTransportsForAssets(request) + -- Check if enough transport units are available. + if _transports==0 then + local text=string.format("Warehouse %s: Request denied! Not enough transport assets currently available.", self.alias) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + + return false + end + + -- Check if at least one transport asset is available. + if #_transports>0 then + + -- Get the attibute of the transport units. + local _transportattribute=_transports[1].attribute + local _transportcategory=_transports[1].category + + -- Check available parking for transport units. + if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then + local Parking=self:_FindParkingForAssets(self.airbase,_transports) + if Parking==nil then + local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + + return false + end + end + + -- Set chosen assets. + request.transportassets=_transports + request.transportattribute=_transportattribute + request.transportcategory=_transportcategory + + else + + -- Not enough or the right transport carriers. + local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.", self.alias) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:E(self.wid..text) + + return false + + + end + elseif false then -- Transports in stock. @@ -3585,7 +3636,8 @@ function WAREHOUSE:_CheckRequestNow(request) local text=string.format("Warehouse %s: Request denied! Not enough transport assets currently available.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) - okay=false + + return false end -- Check if at least one transport asset is available. @@ -3602,7 +3654,8 @@ function WAREHOUSE:_CheckRequestNow(request) local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) - okay=false + + return false end end @@ -3612,7 +3665,7 @@ function WAREHOUSE:_CheckRequestNow(request) end - return okay + return true end ---Get (optimized) transport carriers for the given assets to be transported. @@ -3623,7 +3676,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- Get all transports of the requested type in stock. local transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype) - local cargoassets=request.assets + local cargoassets=request.cargoassets -- Problems/questions -- 1. Do we have at least one carrier big enough to transport the largest group? @@ -3647,6 +3700,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- The most simple way is to sort the transports in descending order wrt. to their cargo bay size. -- Use largest carriers available until either number of cargo is done in one run or we hit max number of carriers available. + -- sort transport carriers w.r.t. cargo bay size. local function sort_transports(a,b) @@ -3663,7 +3717,9 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- Very simple! Only take the largest transports that can carrier the largest cargo. local used_transports={} - local maxcargoweight=cargoassets[1] + + local maxcargoweight=cargoassets[1].weight + for i=1,#transports do local transport=transports[i] --#WAREHOUSE.Assetitem if transport.cargobay>maxcargoweight and #used_transports<=request.ntransport then @@ -3675,7 +3731,8 @@ function WAREHOUSE:_GetTransportsForAssets(request) local transport=_transport --#WAREHOUSE.Assetitem --env.info("transport used = ", transport.) end - + + return used_transports end ---Sorts the queue and checks if the request can be fulfilled. @@ -3726,17 +3783,20 @@ end --- Simple task function. Can be used to call a function which has the warehouse and the executing group as parameters. -- @param #WAREHOUSE self -- @param #string Function The name of the function to call passed as string. -function WAREHOUSE:_SimpleTaskFunction(Function) +-- @param Wrapper.Group#GROUP group The group which is meant. +function WAREHOUSE:_SimpleTaskFunction(Function, group) self:F2({Function}) -- Name of the warehouse (static) object. local warehouse=self.warehouse:GetName() + local groupname=group:GetName() -- Task script. local DCSScript = {} - DCSScript[#DCSScript+1] = string.format('env.info("WAREHOUSE: Simple task function called!") ') - DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:Find( ... ) ') -- The group that executes the task function. Very handy with the "...". - DCSScript[#DCSScript+1] = string.format('local mystatic=STATIC:FindByName(%s) ', warehouse) -- The static that holds the warehouse self object. + --DCSScript[#DCSScript+1] = string.format('env.info("WAREHOUSE: Simple task function called!") ') + --DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:Find( ... ) ') -- The group that executes the task function. Very handy with the "...". + DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(%s) ', groupname) -- The group that executes the task function. Very handy with the "...". + DCSScript[#DCSScript+1] = string.format('local mystatic = STATIC:FindByName(%s) ', warehouse) -- The static that holds the warehouse self object. DCSScript[#DCSScript+1] = string.format('local warehouse = mygroup:GetState(mystatic, "WAREHOUSE") ') -- Get the warehouse self object from the static. DCSScript[#DCSScript+1] = string.format('%s(warehouse, mygroup)', Function) -- Call the function, e.g. myfunction.(warehouse,mygroup) From 1ba84003d376fdebd5ec28421f9a1a0f08232614 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 30 Aug 2018 16:40:59 +0200 Subject: [PATCH 37/73] Warehouse v0.3.0w --- .../Moose/Functional/Warehouse.lua | 44 ++++--------------- 1 file changed, 9 insertions(+), 35 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index a56574a36..a1f1efdeb 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -54,18 +54,6 @@ -- -- === -- --- # Demo Missions --- --- ### None. --- --- === --- --- # YouTube Channel --- --- ### None. --- --- === --- -- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.jpg) -- -- # The Warehouse Concept @@ -495,7 +483,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.0" +WAREHOUSE.version="0.3.0w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1804,17 +1792,11 @@ end -- @param #string Assignment A keyword or text that function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Assignment, Prio) - -- Defaults. - nAsset=nAsset or 1 - TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED - Prio=Prio or 50 - if nTransport==nil then - if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then - nTransport=0 - else - nTransport=1 - end - end + -- Self request? + local toself=false + if self.warehouse:GetName()==warehouse:GetName() then + toself=true + end -- Increase id. self.queueid=self.queueid+1 @@ -1832,7 +1814,9 @@ function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor transporttype=TransportType, ntransport=nTransport, ndelivered=0, - ntransporthome=0 + ntransporthome=0, + assets={}, + toself=toself, } --#WAREHOUSE.Queueitem -- Add request to queue. @@ -1884,16 +1868,6 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) end - -- Init asset table. - Request.assets={} - - -- Init self request. - if self.warehouse:GetName()==Request.warehouse.warehouse:GetName() then - Request.toself=true - else - Request.toself=false - end - return true end From d51690a3cfe81f13e456e2a683dc9f5761e1ca02 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 31 Aug 2018 00:42:15 +0200 Subject: [PATCH 38/73] Warehouse v0.3.1 Ships back to stock is now working. Fixed some bugs. --- .../Moose/Functional/Warehouse.lua | 197 ++++++++++-------- 1 file changed, 105 insertions(+), 92 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index a1f1efdeb..7e2afb5ee 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -48,6 +48,7 @@ -- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @field Core.Zone#ZONE portzone Zone defining the port of a warehouse. This is where naval assets are spawned. -- @field #table shippinglanes Table holding the user defined shipping between warehouses. +-- @field #boolean selfdefence When the warehouse is under attack, automatically spawn assets to defend the warehouse. -- @extends Core.Fsm#FSM --- Have your assets at the right place at the right time - or not! @@ -321,32 +322,33 @@ -- -- @field #WAREHOUSE WAREHOUSE = { - ClassName = "WAREHOUSE", - Debug = false, - Report = true, - warehouse = nil, - coalition = nil, - country = nil, - alias = nil, - zone = nil, - airbase = nil, - airbasename = nil, - category = -1, - coordinate = nil, - road = nil, - rail = nil, - spawnzone = nil, - wid = nil, - uid = nil, - markerid = nil, - dTstatus = 30, - queueid = 0, - stock = {}, - queue = {}, - pending = {}, - defending = {}, - portzone = nil, - shippinglanes = {}, + ClassName = "WAREHOUSE", + Debug = false, + Report = true, + warehouse = nil, + coalition = nil, + country = nil, + alias = nil, + zone = nil, + airbase = nil, + airbasename = nil, + category = -1, + coordinate = nil, + road = nil, + rail = nil, + spawnzone = nil, + wid = nil, + uid = nil, + markerid = nil, + dTstatus = 30, + queueid = 0, + stock = {}, + queue = {}, + pending = {}, + defending = {}, + portzone = nil, + shippinglanes = {}, + selfdefence = false, } --- Item of the warehouse stock table. @@ -369,15 +371,16 @@ WAREHOUSE = { --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem -- @field #number uid Unique id of the queue item. --- @field #number prio Priority of the request. -- @field #WAREHOUSE warehouse Requesting warehouse. --- @field Wrapper.Airbase#AIRBASE airbase Requesting airbase or airbase beloning to requesting warehouse. --- @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. Enumerator of type @{#WAREHOUSE.Descriptor}. -- @field assetdescval Value of the asset descriptor. Type depends on "assetdesc" descriptor. -- @field #number nasset Number of asset groups requested. -- @field #WAREHOUSE.TransportType transporttype Transport unit type. -- @field #number ntransport Max. number of transport units requested. +-- @field #string assignment A keyword or text that later be used to identify this request and postprocess the assets. +-- @field #number prio Priority of the request. Number between 1 (high) and 100 (low). +-- @field Wrapper.Airbase#AIRBASE airbase The airbase beloning to requesting warehouse if any. +-- @field DCS#Airbase.Category category Category of the requesting airbase, i.e. airdrome, helipad/farp or ship. -- @field #boolean toself Self request, i.e. warehouse requests assets from itself. -- @field #table assets Table of self propelled (or cargo) and transport assets. Each element of the table is a @{#WAREHOUSE.Assetitem} and can be accessed by their asset ID. -- @field #table cargoassets Table of cargo (or self propelled) assets. Each element of the table is a @{#WAREHOUSE.Assetitem}. @@ -483,14 +486,16 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.0w" +WAREHOUSE.version="0.3.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Warehouse re-capturing not working?! --- TODO: Naval assets dont go back into stock once arrived. +-- TODO: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? +-- TODO: Add autoselfdefence switch and user function. Default should be off. +-- DONE: Warehouse re-capturing not working?! +-- DONE: Naval assets dont go back into stock once arrived. -- TODO: Take cargo weight into consideration, when selecting transport assets. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- DONE: Add ports for spawning naval assets. @@ -500,14 +505,14 @@ WAREHOUSE.version="0.3.0w" -- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. -- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. --- TODO: Handle cases with immobile units. +-- DONE: Handle cases with immobile units <== should be handled by dispatcher classes. -- TODO: Handle cargo crates. -- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? -- TODO: Add general message function for sending to coaliton or debug. -- TODO: Fine tune event handlers. -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. -- DONE: Improve generalized attributes. --- TODO: Add a time stamp when an asset is added to the stock and for requests +-- TODO: Add a time stamp when an asset is added to the stock and for requests. -- DONE: If warehouse is destoyed, all asssets are gone. -- DONE: Add event handlers. -- DONE: Add AI_CARGO_AIRPLANE @@ -676,6 +681,7 @@ function WAREHOUSE:New(warehouse, alias) -- @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 #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. -- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. --- Triggers the FSM event "AddRequest" with a delay. Add a request to the warehouse queue, which is processed when possible. @@ -688,6 +694,7 @@ function WAREHOUSE:New(warehouse, alias) -- @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 #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. -- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. @@ -1708,21 +1715,9 @@ end -- @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. --- @param #string Assignment A keyword or text that +-- @param #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. -- @return #boolean If true, request is okay at first glance. function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Assignment, Prio) - - -- Defaults. - nAsset=nAsset or 1 - TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED - Prio=Prio or 50 - if nTransport==nil then - if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then - nTransport=0 - else - nTransport=1 - end - end -- Request is okay. local okay=true @@ -1788,16 +1783,33 @@ end -- @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 #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. -- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. --- @param #string Assignment A keyword or text that function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor, AssetDescriptorValue, nAsset, TransportType, nTransport, Assignment, Prio) + -- Defaults. + nAsset=nAsset or 1 + TransportType=TransportType or WAREHOUSE.TransportType.SELFPROPELLED + Prio=Prio or 50 + if nTransport==nil then + if TransportType==WAREHOUSE.TransportType.SELFPROPELLED then + nTransport=0 + else + nTransport=1 + end + end + + -- Not more transports than assets. + --if type(nAsset)=="number" then + -- nTransport=math.min(nAsset, nTransport) + --end + -- Self request? local toself=false - if self.warehouse:GetName()==warehouse:GetName() then + if self.warehouse:GetName()==warehouse.warehouse:GetName() then toself=true - end - + end + -- Increase id. self.queueid=self.queueid+1 @@ -1806,13 +1818,14 @@ function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor uid=self.queueid, prio=Prio, warehouse=warehouse, - airbase=warehouse.airbase, - category=warehouse.category, assetdesc=AssetDescriptor, assetdescval=AssetDescriptorValue, nasset=nAsset, transporttype=TransportType, ntransport=nTransport, + assignment=tostring(Assignment), + airbase=warehouse.airbase, + category=warehouse.category, ndelivered=0, ntransporthome=0, assets={}, @@ -2544,7 +2557,9 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) self:I(self.wid..text) -- Spawn all ground units in the spawnzone? - self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) + if self.selfdefence then + self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) + end end --- On after "Defeated" event. Warehouse defeated an attack by another coalition. Defender assets are added back to warehouse stock. @@ -2561,10 +2576,7 @@ function WAREHOUSE:onafterDefeated(From, Event, To) --if self.defenderrequest then for _,request in pairs(self.defending) do - - -- Get all assets that were deployed for defending the warehouse. - --local request=self.defenderrequest --#WAREHOUSE.Pendingitem - + -- Route defenders back to warehoue (for visual reasons only) and put them back into stock. for _,_group in pairs(request.cargogroupset:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP @@ -2579,9 +2591,6 @@ function WAREHOUSE:onafterDefeated(From, Event, To) self:__AddAsset(60, group) end - -- Set defender request back to nil. - --self.defenderrequest=nil - --self:_DeleteQueueItem(request, self.defending) end @@ -2664,7 +2673,7 @@ end function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) -- Message. - local text=string.format("Warehouse %s: We recaptured our airbase %d from the enemy (coalition=%d)!", self.alias, self.airbasename, Coalition) + local text=string.format("Warehouse %s: We recaptured our airbase %s from the enemy (coalition=%d)!", self.alias, self.airbasename, Coalition) MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:I(self.wid..text) @@ -2771,9 +2780,8 @@ function WAREHOUSE:_RouteNaval(group, request) end -- Task function triggering the arrived event at the last waypoint. - --local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) - local TaskFunction = self:_SimpleTaskFunction("WAREHOUSE:_ArrivedSimple", group) - + local TaskFunction = self:_SimpleTaskFunction("warehouse:_ArrivedSimple", group) + -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] group:SetTaskWaypoint(Waypoint, TaskFunction) @@ -3046,40 +3054,39 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventBaseCaptured(EventData) + self:T3(self.wid..string.format("Warehouse %s captured event base captured!",self.alias)) - -- This warehouse does not have an airbase and never had one. So i could not be captured. + -- This warehouse does not have an airbase and never had one. So it could not have been captured. if self.airbasename==nil then - -- This warehouse never had an airbase so I cannot have been captured. return end - self:E(self.wid..string.format("Warehouse %s captured event base captured!",self.alias)) - if EventData and EventData.Place then -- Place is the airbase that was captured. local airbase=EventData.Place --Wrapper.Airbase#AIRBASE + -- Check that this airbase belongs or did belong to this warehouse. if EventData.PlaceName==self.airbasename then - -- Okay, this airbase belongs or did belong to this warehouse. - - self:I(self.wid..string.format("Airbase of warehouse %s was captured! ",self.alias)) - + -- New coalition of airbase after it was captured. - local coalitionAirbase=airbase:GetCoalition() + local NewCoalitionAirbase=airbase:GetCoalition() + -- Debug info + self:I(self.wid..string.format("Airbase of warehouse %s (coalition = %d) was captured! New owner coalition = %d.",self.alias, self.coalition, NewCoalitionAirbase)) + -- So what can happen? -- Warehouse is blue, airbase is blue and belongs to warehouse and red captures it ==> self.airbase=nil -- Warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place if self.airbase==nil then - -- Warehouse lost this airbase previously and not it was re-captured. - if coalitionAirbase == self.coalition then - self:AirbaseRecaptured(coalitionAirbase) + -- New coalition is the same as of the warehouse ==> warehouse previously lost this airbase and now it was re-captured. + if NewCoalitionAirbase == self.coalition then + self:AirbaseRecaptured(NewCoalitionAirbase) end else -- Captured airbase belongs to this warehouse but was captured by other coaltion. - if coalitionAirbase ~= self.coalition then - self:AirbaseCaptured(coalitionAirbase) + if NewCoalitionAirbase ~= self.coalition then + self:AirbaseCaptured(NewCoalitionAirbase) end end @@ -3353,7 +3360,9 @@ function WAREHOUSE:_CheckRequestValid(request) else - -- Check if enough parking spots are available + -- Check if enough parking spots are available. This checks the spots available in general, i.e. not the free spots. + -- TODO: For FARPS/ships, is it possible to send more assets than parking spots? E.g. a FARPS has only four (or even one). + -- TODO: maybe only check if spots > 0 for the necessary terminal type? At least for FARPS. -- Get necessary terminal type. local termtype=self:_GetTerminal(asset.attribute) @@ -3362,15 +3371,18 @@ function WAREHOUSE:_CheckRequestValid(request) local np_departure=self.airbase:GetParkingSpotsNumber(termtype) local np_destination=request.airbase:GetParkingSpotsNumber(termtype) + -- Debug info. + self:E(string.format("Asset attribute = %s, terminal type = %d, spots at departure = %d, destination = %d", asset.attribute, termtype, np_departure, np_destination)) + -- Not enough parking at sending warehouse. if np_departure < request.nasset then - self:E("ERROR: Incorrect request. No enough parking spots of terminal type at warehouse.") + self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots = %d.", termtype, np_departure)) valid=false end -- Not enough parking at requesting warehouse. if np_destination < request.nasset then - self:E("ERROR: Incorrect request. No enough parking spots of terminal type at requesting warehouse.") + self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at requesting warehouse. Available spots = %d.", termtype, np_destination)) valid=false end @@ -3483,7 +3495,7 @@ function WAREHOUSE:_CheckRequestValid(request) -- Add request as unvalid and delete it later. if valid==false then - self:E(self.wid..string.format("Got invalid request id=%d.", request.uid)) + self:E(self.wid..string.format("ERROR: Got invalid request id=%d.", request.uid)) else self:T3(self.wid..string.format("Got valid request id=%d.", request.uid)) end @@ -3733,7 +3745,7 @@ function WAREHOUSE:_CheckQueue() -- Remember invalid request and delete later in order not to confuse the loop. if not valid then - table.insert(invalid,request) + table.insert(invalid, qitem) end -- Get the first valid request that can be executed now. @@ -3748,7 +3760,7 @@ function WAREHOUSE:_CheckQueue() for _,_request in pairs(invalid) do self:E(self.wid..string.format("Deleting invalid request id=%d.",_request.uid)) self:_DeleteQueueItem(_request, self.queue) - end + end -- Execute request. return request @@ -3767,12 +3779,11 @@ function WAREHOUSE:_SimpleTaskFunction(Function, group) -- Task script. local DCSScript = {} - --DCSScript[#DCSScript+1] = string.format('env.info("WAREHOUSE: Simple task function called!") ') - --DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:Find( ... ) ') -- The group that executes the task function. Very handy with the "...". - DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(%s) ', groupname) -- The group that executes the task function. Very handy with the "...". - DCSScript[#DCSScript+1] = string.format('local mystatic = STATIC:FindByName(%s) ', warehouse) -- The static that holds the warehouse self object. - DCSScript[#DCSScript+1] = string.format('local warehouse = mygroup:GetState(mystatic, "WAREHOUSE") ') -- Get the warehouse self object from the static. - DCSScript[#DCSScript+1] = string.format('%s(warehouse, mygroup)', Function) -- Call the function, e.g. myfunction.(warehouse,mygroup) + --DCSScript[#DCSScript+1] = string.format('env.info(\"WAREHOUSE: Simple task function called!\") ') + DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...". + DCSScript[#DCSScript+1] = string.format("local mystatic = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object. + DCSScript[#DCSScript+1] = string.format('local warehouse = mystatic:GetState(mystatic, \"WAREHOUSE\") ') -- Get the warehouse self object from the static. + DCSScript[#DCSScript+1] = string.format('%s(mygroup)', Function) -- Call the function, e.g. myfunction.(warehouse,mygroup) -- Create task. local DCSTask = CONTROLLABLE.TaskWrappedAction(self, CONTROLLABLE.CommandDoScript(self, table.concat(DCSScript))) @@ -4218,7 +4229,9 @@ function WAREHOUSE:_GetAttribute(groupname) elseif tanker then attribute=WAREHOUSE.Attribute.AIR_TANKER elseif transporthelo then - attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO + attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO + elseif attackhelicopter then + attribute=WAREHOUSE.Attribute.AIR_ATTACKHELO elseif apc then attribute=WAREHOUSE.Attribute.GROUND_APC elseif truck then From 075fe729aa7cd379594614723e1893afd786a3d2 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 31 Aug 2018 16:21:13 +0200 Subject: [PATCH 39/73] Warehouse v0.3.1w --- .../Moose/Functional/Warehouse.lua | 190 ++++++++++-------- 1 file changed, 101 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 7e2afb5ee..3e6f7f219 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -486,7 +486,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.1" +WAREHOUSE.version="0.3.1w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -3564,18 +3564,9 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check that a transport units. if request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then - + -- Get best transports for this asset pack. local _transports=self:_GetTransportsForAssets(request) - -- Check if enough transport units are available. - if _transports==0 then - local text=string.format("Warehouse %s: Request denied! Not enough transport assets currently available.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - - return false - end - -- Check if at least one transport asset is available. if #_transports>0 then @@ -3607,47 +3598,12 @@ function WAREHOUSE:_CheckRequestNow(request) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:E(self.wid..text) - return false - - + return false end - - elseif false then - - -- Transports in stock. - local _transports,_ntransports,_enough=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) - -- Check if enough transport units are available. - if not _enough then - local text=string.format("Warehouse %s: Request denied! Not enough transport assets currently available.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - - return false - end - - -- Check if at least one transport asset is available. - if _ntransports>0 then - - -- Get the attibute of the transport units. - local _transportattribute=_transports[1].attribute - local _transportcategory=_transports[1].category - - -- Check available parking for transport units. - if self.airbase and (_transportcategory==Group.Category.AIRPLANE or _transportcategory==Group.Category.HELICOPTER) then - local Parking=self:_FindParkingForAssets(self.airbase,_transports) - if Parking==nil then - local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) - - return false - end - end - - end else - -- self propelled case. + + -- Self propelled case. Nothing to do for now. end @@ -3661,63 +3617,119 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- Get all transports of the requested type in stock. local transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype) - - local cargoassets=request.cargoassets - - -- Problems/questions - -- 1. Do we have at least one carrier big enough to transport the largest group? - -- If not ==> No transport possible since groups cannot be split! - -- If yes ==> Tranport possible. - -- 2. How many carriers do we need? - -- ntransport should be the max number. - - -- Example 8, 8, 5, 3 - -- Carriers: - -- 2 that can take 8 can be used for 3, 5, 8 - -- 1 that can take 6 can be used for 3, 5, - - -- 3 that can take 4 can be used for 3, -, - - -- 1 that can take 2 can be used for -, -, - - - -- So the problem becomes: - -- How do I minimize the number of "ways" with the constraint of a fixed number of carriers? - -- Extreme cases: - -- Use just one carrier that can carrier the largest group. I would have to drive n times to get all cargo from A to B. - -- - - -- The most simple way is to sort the transports in descending order wrt. to their cargo bay size. - -- Use largest carriers available until either number of cargo is done in one run or we hit max number of carriers available. - - - -- sort transport carriers w.r.t. cargo bay size. + + -- Copy asset. + local cargoassets=UTILS.DeepCopy(request.cargoassets) + + -- Sort transport carriers w.r.t. cargo bay size. local function sort_transports(a,b) - return a.cargobay>b.cargobay + return a.cargobaymax>b.cargobaymax end - -- sort cargo assets w.r.t. weight in assending order + -- Sort cargo assets w.r.t. weight in assending order. local function sort_cargoassets(a,b) return a.weight>b.weight end + -- Sort tables. table.sort(transports, sort_transports) table.sort(cargoassets, sort_cargoassets) + + -- Total cargo bay size of all groups. + env.info("Transport capability:") + local totalbay=0 + for i=1,#transports do + local transport=transports[i] --#WAREHOUSE.Assetitem + for j=1,transport.nunits do + totalbay=totalbay+transport.cargobay[j] + env.info(string.format("Cargo bay = %d (unit=%d)", transport.cargobay[j], j)) + end + end + env.info(string.format("Total capacity = %d", totalbay)) - -- Very simple! Only take the largest transports that can carrier the largest cargo. + -- Total cargo weight. + env.info("Cargo weight:") + local totalweight=0 + for i=1,#cargoassets do + local asset=cargoassets[i] --#WAREHOUSE.Assetitem + totalweight=totalweight+asset.weight + env.info(string.format("weight = %d", asset.weight)) + end + env.info(string.format("Total weight = %d", totalweight)) + + -- Transports used. local used_transports={} - local maxcargoweight=cargoassets[1].weight - + -- Loop over all transport groups, largest cargobaymax to smallest. for i=1,#transports do - local transport=transports[i] --#WAREHOUSE.Assetitem - if transport.cargobay>maxcargoweight and #used_transports<=request.ntransport then + + -- Shortcut for carrier and cargo bay + local transport=transports[i] + + -- Cargo put into carrier. + local putintocarrier={} + + -- Cargo assigned to this transport group? + local used=false + + -- Loop over all units + for k=1,transport.nunits do + + -- Get cargo bay of this carrier. + local cargobay=transport.cargobay[k] + + -- Loop over cargo assets. + for j,asset in pairs(cargoassets) do + + -- How many times does the cargo fit into the carrier? + local n=cargobay/asset.weight + + -- Cargo fits into carrier + if n>=1 then + -- Reduce remaining cargobay. + cargobay=cargobay-asset.weight + env.info(string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) + + -- Remember this cargo and remove it so it does not get loaded into other carriers. + table.insert(putintocarrier, j) + + -- This transport group is used. + used=true + end + + end -- loop over assets + end -- loop over units + + -- Remove cargo assets from list. Needs to be done back-to-front in oder not to confuse the loop. + for j=#putintocarrier,1, -1 do + local nput=putintocarrier[j] + + local cargo=cargoassets[nput] + env.info(string.format("cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) + + table.remove(cargoassets, nput) + end + + -- Cargo was assined for this carrier. + if used then table.insert(used_transports, transport) end + + -- Max number of transport groups reached? + if #used_transports>=request.ntransport then + break + end end - for _,_transport in ipairs(used_transports) do - local transport=_transport --#WAREHOUSE.Assetitem - --env.info("transport used = ", transport.) - end - + -- Debug info. + env.info("Used Transports:") + for _,transport in pairs(used_transports) do + env.info(string.format("%s, cargobaymax=%d, nunits=%d", transport.templatename, transport.cargobaymax, transport.nunits)) + for _,cargobay in pairs(transport.cargobay) do + env.info(string.format("cargobay %d", cargobay)) + end + end + return used_transports end From c1dee544931dadb8c77aa439db229edd8fa67562 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 2 Sep 2018 00:07:49 +0200 Subject: [PATCH 40/73] Warehouse v0.3.2 Added new flightplan. Fixed some bugs. --- .../Moose/Functional/Warehouse.lua | 419 ++++++++++++------ 1 file changed, 278 insertions(+), 141 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3e6f7f219..9feccd938 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -366,7 +366,9 @@ WAREHOUSE = { -- @field DCS#Object.Desc DCSdesc All DCS descriptors. -- @field #WAREHOUSE.Attribute attribute Generalized attribute of the group. -- @field #boolean transporter If true, the asset is able to transport troops. --- @field #number cargobay Weight in kg that fits in the cargo bay of one asset unit. +-- @field #table cargobay Array of cargo bays of all units in an asset group. +-- @field #number cargobaytot Total weight in kg that fits in the cargo bay of all asset group units. +-- @field #number cargobaymax Largest cargo bay of all units in the group. --- Item of the warehouse queue table. -- @type WAREHOUSE.Queueitem @@ -442,19 +444,19 @@ WAREHOUSE.Attribute = { AIR_TANKER="Air_Tanker", AIR_TRANSPORTHELO="Air_TransportHelo", AIR_ATTACKHELO="Air_AttackHelo", - AIR_OTHER="Air_Other", + AIR_OTHER="Air_OtherAir", GROUND_APC="Ground_APC", GROUND_TRUCK="Ground_Truck", GROUND_INFANTRY="Ground_Infantry", GROUND_ARTILLERY="Ground_Artillery", GROUND_TANK="Ground_Tank", GROUND_TRAIN="Ground_Train", - GROUND_OTHER="Ground_Other", + GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", NAVAL_ARMEDSHIP="Naval_ArmedShip", NAVAL_UNARMEDSHIP="Naval_UnarmedShip", - NAVAL_OTHER="Naval_Other", + NAVAL_OTHER="Naval_OtherNaval", UNKNOWN="Unknown", } @@ -486,7 +488,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.1w" +WAREHOUSE.version="0.3.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1276,7 +1278,8 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu group=GROUP:FindByName(group) end - self:E(string.format("Adding %d assets of group %s.", n, group:GetName())) + -- Debug info. + self:I(self.wid..string.format("Adding %d assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias)) if group then @@ -1314,7 +1317,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Need to create a "zombie" template group maybe? if group:IsAlive()==true then self:E(self.wid..string.format("Destroying group %s.", group:GetName())) - group:Destroy() + group:Destroy(true) end end @@ -1382,8 +1385,10 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) -- Get weight in kg env.info("FF get weight") local weight=0 - local cargobay=0 - for _,_unit in pairs(group:GetUnits()) do + local cargobay={} + local cargobaytot=0 + local cargobaymax=0 + for _i,_unit in pairs(group:GetUnits()) do local unit=_unit --Wrapper.Unit#UNIT local Desc=unit:GetDesc() self:E({UnitDesc=Desc}) @@ -1391,8 +1396,14 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) if unitweight then weight=weight+unitweight env.info("FF weight = "..weight) - end - cargobay=unit:GetCargoBayFreeWeight() + end + local bay=unit:GetCargoBayFreeWeight() + env.info("FF cargo bay = "..bay) + table.insert(cargobay, bay) + cargobaytot=cargobaytot+bay + if bay>cargobaymax then + cargobaymax=bay + end end -- Set/get the generalized attribute. @@ -1423,6 +1434,8 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) asset.attribute=attribute asset.transporter=false -- not used yet asset.cargobay=cargobay + asset.cargobaytot=cargobaytot + asset.cargobaymax=cargobaymax if i==1 then self:_AssetItemInfo(asset) @@ -1453,7 +1466,8 @@ function WAREHOUSE:_AssetItemInfo(asset) text=text..string.format("Range max = %5.2f km\n", asset.range/1000) text=text..string.format("Size max = %5.2f m\n", asset.size) text=text..string.format("Weight total = %5.2f kg\n", asset.weight) - text=text..string.format("Cargo bay = %5.2f kg\n", asset.cargobay) + text=text..string.format("Cargo bay tot = %5.2f kg\n", asset.cargobaytot) + text=text..string.format("Cargo bay max = %5.2f kg\n", asset.cargobaymax) self:E(self.wid..text) self:E({DCSdesc=asset.DCSdesc}) self:E({Template=asset.template}) @@ -1575,7 +1589,7 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) local AirbaseCategory = self.category -- Check enough parking spots. - if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then + if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then --TODO Figure out what's necessary in this case. else @@ -1585,6 +1599,7 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) self:E(text) return nil end + end -- Position the units. @@ -2010,15 +2025,15 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local _nearradius=nil if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - _loadradius=5000 + _loadradius=10000 elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - _loadradius=500 + _loadradius=1000 elseif Request.transporttype==WAREHOUSE.TransportType.APC then - _loadradius=100 + _loadradius=1000 end -- Empty cargo group set. - CargoGroups = SET_CARGO:New() + CargoGroups = SET_CARGO:New():FilterDeads() -- Add cargo groups to set. for _i,_group in pairs(_spawngroups:GetSetObjects()) do @@ -2214,6 +2229,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get group obejet. local group=Cargo:GetObject() --Wrapper.Group#GROUP + + --Cargo:Load() -- Get warehouse state. local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE @@ -2273,8 +2290,8 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} - if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_FindParkingForAssets(self.airbase,_assetstock) + if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + Parking=self:_FindParkingForAssets(self.airbase,_assetstock) or {} end -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. @@ -2312,7 +2329,11 @@ function WAREHOUSE:_SpawnAssetRequest(Request) --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! -- Spawn air units. - _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[_assetitem.uid], UnControlled) + if Parking[_assetitem.uid] then + _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[_assetitem.uid], UnControlled) + else + _group=self:_SpawnAssetAircraft(_assetitem, Request, nil, UnControlled) + end elseif _assetitem.category==Group.Category.TRAIN then @@ -2905,17 +2926,22 @@ function WAREHOUSE:_OnEventArrived(EventData) -- If all IDs are good we can assume it is a warehouse asset. if wid~=nil and aid~=nil and rid~=nil then - -- Debug info. - local text=string.format("Air asset group %s arrived at warehouse %s.", group:GetName(), self.alias) - --MESSAGE:New - self:E(self.wid..text) - - -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. - -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since - -- some units might still be taxiing or whatever. Therefore, we add 10 seconds for each additional unit of the group until the first arrived event is triggered. - local nunits=#group:GetUnits() - local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. - self:__Arrived(dt, group) + -- Check that warehouse ID is right. + if self.uid==wid then + + -- Debug info. + local text=string.format("Air asset group %s arrived at warehouse %s.", group:GetName(), self.alias) + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) + + -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. + -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since + -- some units might still be taxiing or whatever. Therefore, we add 10 seconds for each additional unit of the group until the first arrived event is triggered. + local nunits=#group:GetUnits() + local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. + self:__Arrived(dt, group) + + end else self:T3(string.format("Group that arrived did not belong to a warehouse. Warehouse ID=%s, Asset ID=%s, Request ID=%s.", tostring(wid), tostring(aid), tostring(rid))) @@ -3375,13 +3401,15 @@ function WAREHOUSE:_CheckRequestValid(request) self:E(string.format("Asset attribute = %s, terminal type = %d, spots at departure = %d, destination = %d", asset.attribute, termtype, np_departure, np_destination)) -- Not enough parking at sending warehouse. + --if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then if np_departure < request.nasset then self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots = %d.", termtype, np_departure)) valid=false end -- Not enough parking at requesting warehouse. - if np_destination < request.nasset then + --if np_destination < request.nasset then + if np_destination == 0 then -- TODO: maybe this is just right for FAPS/SHIPS self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at requesting warehouse. Available spots = %d.", termtype, np_destination)) valid=false end @@ -3544,7 +3572,10 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check available parking for air asset units. if self.airbase and (_assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER) then + local Parking=self:_FindParkingForAssets(self.airbase,_assets) + + --if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then if Parking==nil then local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all assets at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) @@ -3552,6 +3583,7 @@ function WAREHOUSE:_CheckRequestNow(request) return false end + end -- Set chosen assets. @@ -3636,26 +3668,26 @@ function WAREHOUSE:_GetTransportsForAssets(request) table.sort(cargoassets, sort_cargoassets) -- Total cargo bay size of all groups. - env.info("Transport capability:") + self:T2(self.wid.."Transport capability:") local totalbay=0 for i=1,#transports do local transport=transports[i] --#WAREHOUSE.Assetitem for j=1,transport.nunits do totalbay=totalbay+transport.cargobay[j] - env.info(string.format("Cargo bay = %d (unit=%d)", transport.cargobay[j], j)) + self:T2(self.wid..string.format("Cargo bay = %d (unit=%d)", transport.cargobay[j], j)) end end - env.info(string.format("Total capacity = %d", totalbay)) + self:T2(self.wid..string.format("Total capacity = %d", totalbay)) - -- Total cargo weight. - env.info("Cargo weight:") - local totalweight=0 + -- Total cargo weight of all assets to transports. + self:T2(self.wid.."Cargo weight:") + local totalcargoweight=0 for i=1,#cargoassets do local asset=cargoassets[i] --#WAREHOUSE.Assetitem - totalweight=totalweight+asset.weight - env.info(string.format("weight = %d", asset.weight)) + totalcargoweight=totalcargoweight+asset.weight + self:T2(self.wid..string.format("weight = %d", asset.weight)) end - env.info(string.format("Total weight = %d", totalweight)) + self:T2(self.wid..string.format("Total weight = %d", totalcargoweight)) -- Transports used. local used_transports={} @@ -3688,7 +3720,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) if n>=1 then -- Reduce remaining cargobay. cargobay=cargobay-asset.weight - env.info(string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) + self:T3(self.wid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) -- Remember this cargo and remove it so it does not get loaded into other carriers. table.insert(putintocarrier, j) @@ -3705,7 +3737,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) local nput=putintocarrier[j] local cargo=cargoassets[nput] - env.info(string.format("cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) + self:T2(self.wid..string.format("Cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) table.remove(cargoassets, nput) end @@ -3716,19 +3748,26 @@ function WAREHOUSE:_GetTransportsForAssets(request) end -- Max number of transport groups reached? - if #used_transports>=request.ntransport then + if #used_transports >= request.ntransport then break end end -- Debug info. - env.info("Used Transports:") - for _,transport in pairs(used_transports) do - env.info(string.format("%s, cargobaymax=%d, nunits=%d", transport.templatename, transport.cargobaymax, transport.nunits)) - for _,cargobay in pairs(transport.cargobay) do - env.info(string.format("cargobay %d", cargobay)) - end - end + local text=string.format("Used Transports for request %d to warehouse %s:\n", request.uid, request.warehouse.alias) + local totalcargobay=0 + for _i,_transport in pairs(used_transports) do + local transport=_transport --#WAREHOUSE.Assetitem + text=text..string.format("%d) %s: cargobay tot = %d kg, cargobay max = %d kg, nunits=%d\n", _i, transport.unittype, transport.cargobaytot, transport.cargobaymax, transport.nunits) + totalcargobay=totalcargobay+transport.cargobaytot + --for _,cargobay in pairs(transport.cargobay) do + -- env.info(string.format("cargobay %d", cargobay)) + --end + end + text=text..string.format("Total cargo bay capacity = %.1f kg\n", totalcargobay) + text=text..string.format("Total cargo weight = %.1f kg\n", totalcargoweight) + text=text..string.format("Minimum number of runs = %.1f", totalcargoweight/totalcargobay) + self:I(self.wid..text) return used_transports end @@ -3753,10 +3792,11 @@ function WAREHOUSE:_CheckQueue() local valid=self:_CheckRequestValid(qitem) -- Check if request is possible now. - local okay=self:_CheckRequestNow(qitem) - - -- Remember invalid request and delete later in order not to confuse the loop. - if not valid then + local okay=false + if valid then + okay=self:_CheckRequestNow(qitem) + else + -- Remember invalid request and delete later in order not to confuse the loop. table.insert(invalid, qitem) end @@ -4408,11 +4448,23 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) -- Get assets in stock. local _data=self:GetStockInfo(self.stock) + --[[ + local function _sort(a,b) + return aDeltaholdingMax then + h_holding=math.abs(DeltaholdingMax) + end + + -- This is the height ASL of the holding point we want to fly to. local Hh_holding=H_holding+h_holding - - -- Distance from holding point to final destination. - local d_holding=Pholding:Get2DDistance(Pdestination) - -- GENERAL - local heading=Pdeparture:HeadingTo(Pdestination) - local d_total=Pdeparture:Get2DDistance(Pholding) + --------------------------- + --- Max Flight Altitude --- + --------------------------- + + -- Get max flight altitude relative to H_departure. + local h_max=self:_MakeFlightplan(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding) - -------------------------------------------- - - -- Height difference between departure and destination. - local deltaH=math.abs(H_departure-Hh_holding) - - -- Slope between departure and destination. - local phi = math.atan(deltaH/d_total) - - -- Adjusted climb/descent angles. - local phi_climb - local phi_descent - if (H_departure > Hh_holding) then - phi_climb=AlphaClimb+phi - phi_descent=AlphaDescent-phi - else - phi_climb=AlphaClimb-phi - phi_descent=AlphaDescent+phi - end - - -- Total distance including slope. - local D_total=math.sqrt(deltaH*deltaH+d_total*d_total) - - -- SSA triangle for sloped case. - local gamma=math.rad(180)-phi_climb-phi_descent - local a = D_total*math.sin(phi_climb)/math.sin(gamma) - local b = D_total*math.sin(phi_descent)/math.sin(gamma) - local hphi_max = b*math.sin(phi_climb) - local hphi_max2 = a*math.sin(phi_descent) - - -- Height of triangle. - local h_max1 = b*math.sin(AlphaClimb) - local h_max2 = a*math.sin(AlphaDescent) - - -- Max height relative to departure or destination. - local h_max - if (H_departure > Hh_holding) then - h_max=math.min(h_max1, h_max2) - else - h_max=math.max(h_max1, h_max2) - end - - -- Max flight level aircraft can reach for given angles and distance. + -- Max flight level ASL aircraft can reach for given angles and distance. local FLmax = h_max+H_departure --CRUISE -- Min cruise alt is just above holding point at destination or departure height, whatever is larger. local FLmin=math.max(H_departure, Hh_holding) - - -- For helicopters we take cruise alt between 50 to 1000 meters above ground. Default cruise alt is ~150 m. - if _category==Group.Category.HELICOPTER then - FLmin=math.max(H_departure, H_destination)+50 - FLmax=math.max(H_departure, H_destination)+1000 - end -- Ensure that FLmax not above its service ceiling. FLmax=math.min(FLmax, ceiling) @@ -4652,28 +4761,38 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) -- Climb and descent heights. local h_climb = FLcruise - H_departure local h_descent = FLcruise - Hh_holding - - -- Distances. + + -- Get distances. local d_climb = h_climb/math.tan(AlphaClimb) local d_descent = h_descent/math.tan(AlphaDescent) local d_cruise = d_total-d_climb-d_descent -- Debug. local text=string.format("Flight plan:\n") - text=text..string.format("Vx max = %d\n", Vmax) - text=text..string.format("Vx climb = %d\n", VxClimb) - text=text..string.format("Vx cruise = %d\n", VxCruise) - text=text..string.format("Vx descent = %d\n", VxDescent) - text=text..string.format("Vx holding = %d\n", VxHolding) - text=text..string.format("Vx final = %d\n", VxFinal) - text=text..string.format("Dist climb = %d\n", d_climb) - text=text..string.format("Dist cruise = %d\n", d_cruise) - text=text..string.format("Dist descent = %d\n", d_descent) - text=text..string.format("Dist total = %d\n", d_total) - text=text..string.format("FL min = %d\n", FLmin) - text=text..string.format("FL cruise * = %d\n", FLcruise) - text=text..string.format("FL max = %d\n", FLmax) - text=text..string.format("Ceiling = %d\n", ceiling) + text=text..string.format("Vx max = %.2f km/h\n", Vmax*3.6) + text=text..string.format("Vx climb = %.2f km/h\n", VxClimb*3.6) + text=text..string.format("Vx cruise = %.2f km/h\n", VxCruise*3.6) + text=text..string.format("Vx descent = %.2f km/h\n", VxDescent*3.6) + text=text..string.format("Vx holding = %.2f km/h\n", VxHolding*3.6) + text=text..string.format("Vx final = %.2f km/h\n", VxFinal*3.6) + text=text..string.format("Vy max = %.2f m/s\n", Vymax) + text=text..string.format("Vy climb = %.2f m/s\n", VyClimb) + text=text..string.format("Alpha Climb = %.2f Deg\n", math.deg(AlphaClimb)) + text=text..string.format("Alpha Descent = %.2f Deg\n", math.deg(AlphaDescent)) + text=text..string.format("Dist climb = %.3f km\n", d_climb/1000) + text=text..string.format("Dist cruise = %.3f km\n", d_cruise/1000) + text=text..string.format("Dist descent = %.3f km\n", d_descent/1000) + text=text..string.format("Dist total = %.3f km\n", d_total/1000) + text=text..string.format("h_climb = %.3f km\n", h_climb/1000) + text=text..string.format("h_desc = %.3f km\n", h_descent/1000) + text=text..string.format("h_holding = %.3f km\n", h_holding/1000) + text=text..string.format("h_max = %.3f km\n", h_max/1000) + text=text..string.format("FL min = %.3f km\n", FLmin/1000) + text=text..string.format("FL expect = %.3f km\n", FLcruise_expect/1000) + text=text..string.format("FL cruise * = %.3f km\n", FLcruise/1000) + text=text..string.format("FL max = %.3f km\n", FLmax/1000) + text=text..string.format("Ceiling = %.3f km\n", ceiling/1000) + text=text..string.format("Max range = %.3f km\n", Range/1000) env.info(text) -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. @@ -4681,6 +4800,10 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) d_cruise=100 end + ------------------------ + --- Create Waypoints --- + ------------------------ + -- Waypoints and coordinates local wp={} local c={} @@ -4721,7 +4844,21 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) --- Final destination. c[#c+1]=Pdestination wp[#wp+1]=Pcruise2:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination") - + + + -- Mark points at waypoints for debugging. + if self.Debug then + for i,coord in pairs(c) do + local coord=coord --Core.Point#COORDINATE + env.info(i) + local dist=0 + if i>1 then + dist=coord:Get2DDistance(c[i-1]) + end + coord:MarkToAll(string.format("Waypoint %i, dist = %.2f km",i, dist/1000)) + end + end + return wp,c end From c9e44dd86511637538f54170954b7b41be45791a Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 2 Sep 2018 23:49:02 +0200 Subject: [PATCH 41/73] Warehosue v0.3.3 --- .../Moose/AI/AI_Cargo_Airplane.lua | 2 +- .../Moose/AI/AI_Cargo_Dispatcher.lua | 52 +-- .../Moose/AI/AI_Cargo_Helicopter.lua | 16 +- Moose Development/Moose/Core/Set.lua | 2 +- .../Moose/Functional/Warehouse.lua | 295 ++++++++++++------ 5 files changed, 239 insertions(+), 128 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index 8ce9bcb42..be200d882 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -132,7 +132,7 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) AirplaneUnit:SetCargoBayWeightLimit() end - self.Relocating = true + self.Relocating = false --FF should be false or set according to state of airplane! return self end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 94e67686a..777c3cff8 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -127,8 +127,8 @@ function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo ) self:AddTransition( "*", "BackHome", "*" ) --FF self.MonitorTimeInterval = 30 - self.DeployRadiusInner = 200 - self.DeployRadiusOuter = 500 + self.DeployInnerRadius = 200 + self.DeployOuterRadius = 500 self.PickupCargo = {} self.CarrierHome = {} @@ -231,19 +231,6 @@ function AI_CARGO_DISPATCHER:SetHomeBase( HomeBase ) 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. -- There are two aspects that are very important to remember and take into account: @@ -368,12 +355,13 @@ end -- @param #AI_CARGO_DISPATCHER self function AI_CARGO_DISPATCHER:onafterMonitor() - env.info("FF number of cargo set = "..self.SetCargo:Count()) - for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do + env.info("FF cargo dispatcher carrier group "..CarrierGroupName) + local Carrier = Carrier -- Wrapper.Group#GROUP local AI_Cargo = self.AI_Cargo[Carrier] if not AI_Cargo then + env.info("FF not AI CARGO") -- ok, so this Carrier does not have yet an AI_CARGO handling object... -- let's create one and also declare the Loaded and UnLoaded handlers. @@ -404,10 +392,15 @@ function AI_CARGO_DISPATCHER:onafterMonitor() self:Unloaded( Carrier, Cargo ) end - -- FF added back home event. + -- FF added BackHome event. function AI_Cargo.OnAfterBackHome( AI_Cargo, Carrier, From, Event, To) self:BackHome( Carrier ) end + + -- FF added RTB event. + function AI_Cargo.OnAfterRTB( AI_Cargo, Carrier, From, Event, To, Airbase) + self:RTB( Carrier, Airbase ) + end end -- The Pickup sequence ... @@ -459,6 +452,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor() end if PickupCargo then + self.CarrierHome[Carrier] = nil local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) @@ -472,18 +466,34 @@ function AI_CARGO_DISPATCHER:onafterMonitor() AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ) ) end break + else + + env.info("FF HomeZone or HomeBase?") if self.HomeZone then + + env.info("FF HomeZone! Really?") if not self.CarrierHome[Carrier] then + env.info("FF Yes!") self.CarrierHome[Carrier] = true AI_Cargo:__Home( 60, self.HomeZone:GetRandomPointVec2() ) + else + env.info("FF Nope!") end - elseif self.HomeBase then + + elseif self.HomeBase2 then + + env.info("FF HomeBase! Really?") if not self.CarrierHome[Carrier] then + env.info("FF Yes!") self.CarrierHome[Carrier] = true - AI_Cargo:__RTB( 60, self.HomeBase ) - end + AI_Cargo:__RTB( 1, self.HomeBase ) + else + env.info("FF Nope!") + end + end + end end end diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index e40f357a4..46cf934a5 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -827,16 +827,6 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) Route[#Route].task = Helicopter:TaskCombo( Tasks ) - -- FF - --[[ - local Tasks2 = {} - Tasks2[#Tasks2+1] = Helicopter:TaskFunction("AI_CARGO_HELICOPTER._BackHome", self) - - Route[#Route+1] = WaypointTo - Route[#Route].task = Helicopter:TaskCombo( Tasks2 ) - -- FF - ]] - Route[#Route+1] = WaypointTo -- Now route the helicopter @@ -930,9 +920,10 @@ end --- Function called when transport is back home and nothing more to do. Triggering the event BackHome. -- @param Wrapper.Group#GROUP Helicopter Cargo helicopter. -- @param #AI_CARGO_HELICOPTER self -function AI_CARGO_HELICOPTER._BackHome(Group, self) - --Trigger BackHome event. +function AI_CARGO_HELICOPTER._BackHome(Group, self) + env.info("FF ai cargo helicopter back home task function") Group:SmokeRed() + --Trigger BackHome event. self:__BackHome(1) end @@ -944,5 +935,6 @@ end -- @param Event -- @param To function AI_CARGO_HELICOPTER:onafterBackHome( Helicopter, From, Event, To ) + env.info("FF ai cargo helicopter back home event") Helicopter:SmokeRed() end \ No newline at end of file diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 7cf4dacc8..410c17c82 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -636,7 +636,7 @@ function SET_BASE:Flush( MasterObject ) for ObjectName, Object in pairs( self.Set ) do ObjectNames = ObjectNames .. ObjectName .. ", " end - self:I( { MasterObject = MasterObject and MasterObject:GetClassNameAndID(), "Objects in Set:", ObjectNames } ) + self:T( { MasterObject = MasterObject and MasterObject:GetClassNameAndID(), "Objects in Set:", ObjectNames } ) return ObjectNames end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 9feccd938..c51d69f4e 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -48,7 +48,7 @@ -- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @field Core.Zone#ZONE portzone Zone defining the port of a warehouse. This is where naval assets are spawned. -- @field #table shippinglanes Table holding the user defined shipping between warehouses. --- @field #boolean selfdefence When the warehouse is under attack, automatically spawn assets to defend the warehouse. +-- @field #boolean autodefence When the warehouse is under attack, automatically spawn assets to defend the warehouse. -- @extends Core.Fsm#FSM --- Have your assets at the right place at the right time - or not! @@ -135,7 +135,7 @@ -- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinize to check if can be fulfilled at all. If the request is valid, it is -- put into the warehouse queue and processed as soon as possible. -- --- A request can be assed by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*) function. +-- A request can be assed by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function. -- The parameters are -- -- * *warehouse*: The requesting MOOSE @{#WAREHOUSE}. Assets will be delivered there. @@ -144,7 +144,8 @@ -- * *nAsset*: (Optional) Number of asset group requested. Default is one group. -- * *TransportType*: (Optional) The transport method used to deliver the assets to the requestor. Default is that assets go to the requesting warehouse on their own. -- * *nTransport*: (Optional) Number of asset groups used to transport the cargo assets from A to B. Default is one group. --- * *Prio*: A number between 1 (high) and 100 (low) describing the priority of the request. Request with high priority are processed first. Default is 50, i.e. medium priority. +-- * *Prio*: (Optional) A number between 1 (high) and 100 (low) describing the priority of the request. Request with high priority are processed first. Default is 50, i.e. medium priority. +-- * *Assignment*: (Optional) A free to choose string describing the assignment. For self requests, this can be used to assign the spawned groups to specific tasks. -- -- So for example: -- @@ -348,7 +349,7 @@ WAREHOUSE = { defending = {}, portzone = nil, shippinglanes = {}, - selfdefence = false, + autodefence = false, } --- Item of the warehouse stock table. @@ -488,7 +489,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.2" +WAREHOUSE.version="0.3.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -498,7 +499,7 @@ WAREHOUSE.version="0.3.2" -- TODO: Add autoselfdefence switch and user function. Default should be off. -- DONE: Warehouse re-capturing not working?! -- DONE: Naval assets dont go back into stock once arrived. --- TODO: Take cargo weight into consideration, when selecting transport assets. +-- DONE: Take cargo weight into consideration, when selecting transport assets. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- DONE: Add ports for spawning naval assets. -- TODO: Added habours as interface for transport to from warehouses? @@ -683,8 +684,8 @@ function WAREHOUSE:New(warehouse, alias) -- @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 #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. -- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. + -- @param #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. --- Triggers the FSM event "AddRequest" with a delay. Add a request to the warehouse queue, which is processed when possible. -- @function [parent=#WAREHOUSE] __AddRequest @@ -696,8 +697,8 @@ function WAREHOUSE:New(warehouse, alias) -- @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 #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. -- @param #number Prio Priority of the request. Number ranging from 1=high to 100=low. + -- @param #string Assignment A keyword or text that later be used to identify this request and postprocess the assets. --- Triggers the FSM event "Request". Executes a request from the queue if possible. @@ -845,7 +846,39 @@ end -- User functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Set interval of status updates +--- Set debug mode on. Error messages will be displayed on screen, units will be smoked at some events. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetDebugOn() + self.Debug=true + return self +end + +--- Set debug mode off. This is the default +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetDebugOff() + self.Debug=false + return self +end + +--- Set report on. Messages at events will be displayed on screen to the coalition owning the warehouse. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetReportOn() + self.Report=true + return self +end + +--- Set report off. Warehouse does not report about its status and at certain events. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetReportOff() + self.Report=false + return self +end + +--- Set interval of status updates. Note that only one request can be processed per time interval. -- @param #WAREHOUSE self -- @param #number timeinterval Time interval in seconds. -- @return #WAREHOUSE self @@ -872,6 +905,23 @@ function WAREHOUSE:SetWarehouseZone(zone) return self end +--- Set auto defence on. When the warehouse is under attack, all ground assets are spawned automatically and will defend the warehouse zone. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetAutoDefenceOn() + self.autodefence=true + return self +end + +--- Set auto defence off. This is the default. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetAutoDefenceOff() + self.autodefence=false + return self +end + + --- Set the airbase belonging to this warehouse. -- Note that it has to be of the same coalition as the warehouse. -- Also, be reasonable and do not put it too far from the phyiscal warehouse structure because you troops might have a long way to get to their transports. @@ -956,8 +1006,6 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group) -- Add the shipping lane. Need to take care of the wrong "direction". local lane={} - --lane.towarehouse=remotewarehouse.warehouse:GetName() - --lane.coordinates={} if distF0 then + self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) + else + local text=string.format("No ground assets currently available.") + MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) + self:I(self.wid..text) + end + else + local text=string.format("Warehouse auto defence inactive.") + self:I(self.wid..text) end end @@ -2595,28 +2681,35 @@ function WAREHOUSE:onafterDefeated(From, Event, To) MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) self:I(self.wid..text) - --if self.defenderrequest then - for _,request in pairs(self.defending) do + -- Debug smoke. + if self.Debug then + self.coordinate:SmokeGreen() + end + + -- Auto defence: put assets back into stock. + if self.autodefence then + for _,request in pairs(self.defending) do + + -- Route defenders back to warehoue (for visual reasons only) and put them back into stock. + for _,_group in pairs(request.cargogroupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + + -- Get max speed of group and route it back slowly to the warehouse. + local speed=group:GetSpeedMax() + if group:IsGround() and speed>1 then + group:RouteGroundTo(self.coordinate, speed*0.3) + end + + -- Add asset group back to stock after 60 seconds. + self:__AddAsset(60, group) + end - -- Route defenders back to warehoue (for visual reasons only) and put them back into stock. - for _,_group in pairs(request.cargogroupset:GetSetObjects()) do - local group=_group --Wrapper.Group#GROUP - - -- Get max speed of group and route it back slowly to the warehouse. - local speed=group:GetSpeedMax() - if group:IsGround() and speed>1 then - group:RouteGroundTo(self.coordinate, speed*0.3) - end - - -- Add asset group back to stock after 60 seconds. - self:__AddAsset(60, group) + --self:_DeleteQueueItem(request, self.defending) end - --self:_DeleteQueueItem(request, self.defending) + self.defending=nil + self.defending={} end - - self.defending=nil - self.defending={} end --- On after "Captured" event. Warehouse has been captured by another coalition. @@ -2643,10 +2736,6 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) -- Delete all waiting requests because they are not valid any more self.queue=nil self.queue={} - - --TODO: What about pending items? Is there any problem due to the coalition change? - --TODO: Maybe if the receiving warehouse gets captured! Oh, oh :( - -- What to do? send the items back? Impossible. -- Airbase could have been captured before and already belongs to the new coalition. local airbase=AIRBASE:FindByName(self.airbasename) @@ -2661,6 +2750,15 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) self.airbase=nil self.category=-1 end + + -- Debug smoke. + if self.Debug then + if Coalition==coalition.side.RED then + self.coordinate:SmokeRed() + elseif Coalition==coalition.side.BLUE then + self.coordinate:SmokeBlue() + end + end end @@ -2678,8 +2776,14 @@ function WAREHOUSE:onafterAirbaseCaptured(From, Event, To, Coalition) self:I(self.wid..text) -- Debug smoke. - self.airbase:GetCoordinate():SmokeRed() - + if self.Debug then + if Coalition==coalition.side.RED then + self.airbase:GetCoordinate():SmokeRed() + elseif Coalition==coalition.side.BLUE then + self.airbase:GetCoordinate():SmokeBlue() + end + end + -- Set airbase to nil and category to no airbase. self.airbase=nil self.category=-1 -- -1 indicates no airbase. @@ -2703,7 +2807,14 @@ function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) self.category=self.airbase:GetDesc().category -- Debug smoke. - self.airbase:GetCoordinate():SmokeGreen() + if self.Debug then + if Coalition==coalition.side.RED then + self.airbase:GetCoordinate():SmokeRed() + elseif Coalition==coalition.side.BLUE then + self.airbase:GetCoordinate():SmokeBlue() + end + end + end @@ -2959,7 +3070,6 @@ function WAREHOUSE:_OnEventBirth(EventData) if EventData and EventData.IniGroup then local group=EventData.IniGroup - -- env.info(string.format("FF birth of group %s (alive=%s) unit %s", tostring(EventData.IniGroupName), tostring(EventData.IniGroup:IsAlive()), tostring(EventData.IniUnitName))) -- Note: Remember, group:IsAlive might(?) not return true here. local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then @@ -2980,7 +3090,7 @@ function WAREHOUSE:_OnEventEngineStartup(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:I(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) end end end @@ -2995,7 +3105,7 @@ function WAREHOUSE:_OnEventTakeOff(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:I(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) end end end @@ -3010,7 +3120,7 @@ function WAREHOUSE:_OnEventLanding(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:I(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) -- Get request of this group local request=self:_GetRequestOfGroup(group,self.pending) @@ -3019,7 +3129,7 @@ function WAREHOUSE:_OnEventLanding(EventData) -- TODO: I might need to add a delivered table, to be better able to get this right. if request==nil then - -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. + -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. if group:GetCategory()==Group.Category.HELICOPTER then if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then group:SmokeWhite() @@ -3542,11 +3652,11 @@ function WAREHOUSE:_CheckRequestNow(request) -- Assume request is okay and check scenarios. local okay=true - -- Check if receiving warehouse is running. - if not request.warehouse:IsRunning() then + -- Check if receiving warehouse is running. We do allow self requests if the warehouse is under attack though! + if (not request.warehouse:IsRunning()) and (not request.toself and self:IsAttacked()) then local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.", self.alias, request.warehouse.alias, request.warehouse:GetState()) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) + self:I(self.wid..text) return false end @@ -3558,7 +3668,7 @@ function WAREHOUSE:_CheckRequestNow(request) if not _enough then local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) + self:I(self.wid..text) return false end @@ -3577,9 +3687,9 @@ function WAREHOUSE:_CheckRequestNow(request) --if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then if Parking==nil then - local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all assets at the moment.", self.alias) + local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) + self:I(self.wid..text) return false end @@ -3612,7 +3722,7 @@ function WAREHOUSE:_CheckRequestNow(request) if Parking==nil then local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) + self:I(self.wid..text) return false end @@ -3628,7 +3738,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Not enough or the right transport carriers. local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.", self.alias) MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) + self:I(self.wid..text) return false end @@ -4173,16 +4283,16 @@ function WAREHOUSE:_FilterStock(stock, item, value, nmax) if type(nmax)=="string" then if nmax:lower()=="all" then nmax=ntot + elseif nmax:lower()=="threequarter" then + nmax=ntot*3/4 elseif nmax:lower()=="half" then nmax=ntot/2 elseif nmax:lower()=="third" then - nmax=ntot/3 + nmax=ntot/3 elseif nmax:lower()=="quarter" then nmax=ntot/4 - elseif nmax:lower()=="fivth" then - nmax=ntot/5 else - nmax=math.min(1,ntot) + nmax=math.min(1, ntot) end end @@ -4229,10 +4339,6 @@ function WAREHOUSE:_GetAttribute(groupname) local attribute=WAREHOUSE.Attribute.UNKNOWN --#WAREHOUSE.Attribute if group then - - -- Get generalized attributes. - -- TODO: need to work on ships and trucks and SAMs and ... - -- Also the Yak-52 for example is OTHER since it only has the attribute "Battleplanes". ----------- --- Air --- @@ -4486,15 +4592,18 @@ function WAREHOUSE:_UpdateWarehouseMarkText() -- Get assets in stock. local _data=self:GetStockInfo(self.stock) - - -- Create mark text. - local marktext="Warehouse stock:\n" - for _attribute,_count in pairs(_data) do - marktext=marktext..string.format("%s=%d, ", _attribute,_count) -- Dont use \n because too many make DCS crash! - end + -- Text. + local text="Warehouse Stock:\n" + text=text..string.format("Total assets: %d\n", #_data) + local total=0 + for _attribute,_count in pairs(_data) do + local attribute=tostring(UTILS.Split(_attribute, "_")[2]) + text=text..string.format("%s=%d", attribute,_count) + end + -- Create/update marker at warehouse in F10 map. - self.markerid=self.coordinate:MarkToCoalition(marktext, self.coalition, true) + self.markerid=self.coordinate:MarkToCoalition(text, self.coalition, true) end --- Display stock items of warehouse. From 14bc7922d02c319f97ba9400bf3b1e374b5af68c Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 3 Sep 2018 16:14:50 +0200 Subject: [PATCH 42/73] Warehouse v0.3.3w --- .../Moose/Functional/Warehouse.lua | 422 ++++++++++++------ 1 file changed, 282 insertions(+), 140 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c51d69f4e..42feeedc6 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -496,7 +496,7 @@ WAREHOUSE.version="0.3.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? --- TODO: Add autoselfdefence switch and user function. Default should be off. +-- DONE: Add autoselfdefence switch and user function. Default should be off. -- DONE: Warehouse re-capturing not working?! -- DONE: Naval assets dont go back into stock once arrived. -- DONE: Take cargo weight into consideration, when selecting transport assets. @@ -510,7 +510,7 @@ WAREHOUSE.version="0.3.3" -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. -- DONE: Handle cases with immobile units <== should be handled by dispatcher classes. -- TODO: Handle cargo crates. --- TODO: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? +-- DONE: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? -- TODO: Add general message function for sending to coaliton or debug. -- TODO: Fine tune event handlers. -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. @@ -599,14 +599,14 @@ function WAREHOUSE:New(warehouse, alias) 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("*", "Stop", "Stopped") -- TODO Stop the warehouse. + self:AddTransition("*", "Stop", "Stopped") -- DONE Stop the warehouse. self:AddTransition("*", "Save", "*") -- TODO Save the warehouse state to disk. - self:AddTransition("*", "Attacked", "Attacked") -- TODO Warehouse is under attack by enemy coalition. - self:AddTransition("Attacked", "Defeated", "Running") -- TODO Attack by other coalition was defeated! - self:AddTransition("Attacked", "Captured", "Running") -- TODO Warehouse was captured by another coalition. It must have been attacked first. - self:AddTransition("*", "AirbaseCaptured", "*") -- TODO Airbase was captured by other coalition. - self:AddTransition("*", "AirbaseRecaptured", "*") -- TODO Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "*") -- TODO Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("*", "Attacked", "Attacked") -- DONE Warehouse is under attack by enemy coalition. + self:AddTransition("Attacked", "Defeated", "Running") -- DONE Attack by other coalition was defeated! + self:AddTransition("Attacked", "Captured", "Running") -- DONE Warehouse was captured by another coalition. It must have been attacked first. + self:AddTransition("*", "AirbaseCaptured", "*") -- DONE Airbase was captured by other coalition. + self:AddTransition("*", "AirbaseRecaptured", "*") -- DONE Airbase was re-captured from other coalition. + self:AddTransition("*", "Destroyed", "*") -- DONE Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. ------------------------ --- Pseudo Functions --- @@ -724,18 +724,34 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. -- @param Wrapper.Group#GROUP group Group that has arrived. + --- On after "Arrived" event user function. Called when a groups has arrived. + -- @function [parent=#WAREHOUSE] OnAfterArrived + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Group#GROUP group Group that has arrived. - --- Triggers the FSM event "Delivered". A group has been delivered from the warehouse to another airbase or warehouse. + + --- Triggers the FSM event "Delivered". A group has been delivered from the warehouse to another warehouse. -- @function [parent=#WAREHOUSE] Delivered -- @param #WAREHOUSE self -- @param #WAREHOUSE.Pendingitem request Pending request that was now delivered. - --- Triggers the FSM event "Delivered" after a delay. A group has been delivered from the warehouse to another airbase or warehouse. + --- Triggers the FSM event "Delivered" after a delay. A group has been delivered from the warehouse to another warehouse. -- @function [parent=#WAREHOUSE] __Delivered -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. -- @param #WAREHOUSE.Pendingitem request Pending request that was now delivered. + --- On after "Delivered" event user function. Called when a group has been delivered from the warehouse to another warehouse. + -- @function [parent=#WAREHOUSE] OnAfterDelivered + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #WAREHOUSE.Pendingitem request Pending request that was now delivered. + --- Triggers the FSM event "SelfRequest". Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. -- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, @@ -777,6 +793,15 @@ function WAREHOUSE:New(warehouse, alias) -- @param DCS#coalition.side Coalition which is attacking the warehouse. -- @param DCS#country.id Country which is attacking the warehouse. + --- On after "Attacked" event user function. Called when a warehouse (zone) is under attack by an enemy. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] OnAfterAttacked + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param DCS#coalition.side Coalition which is attacking the warehouse. + -- @param DCS#country.id Country which is attacking the warehouse. + --- Triggers the FSM event "Defeated" when an attack from an enemy was defeated. -- @param #WAREHOUSE self @@ -791,6 +816,15 @@ function WAREHOUSE:New(warehouse, alias) -- @param DCS#coalition.side Coalition which is attacking the warehouse. -- @param DCS#country.id Country which is attacking the warehouse. + --- On after "Defeated" event user function. Called when an enemy attack was defeated. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] OnAfterDefeated + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param DCS#coalition.side Coalition which is attacking the warehouse. + -- @param DCS#country.id Country which is attacking the warehouse. + --- Triggers the FSM event "Captured" when a warehouse has been captured by another coalition. -- @param #WAREHOUSE self @@ -805,6 +839,15 @@ function WAREHOUSE:New(warehouse, alias) -- @param DCS#coalition.side Coalition which captured the warehouse. -- @param DCS#country.id Country which has captured the warehouse. + --- On after "Captured" event user function. Called when the warehouse has been captured by an enemy coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] OnAfterCaptured + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param DCS#coalition.side Coalition which captured the warehouse. + -- @param DCS#country.id Country which has captured the warehouse. + -- --- Triggers the FSM event "AirbaseCaptured" when the airbase of the warehouse has been captured by another coalition. -- @param #WAREHOUSE self @@ -817,6 +860,14 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. -- @param DCS#coalition.side Coalition which captured the airbase. + --- On after "AirbaseCaptured" even user function. Called when the airbase of the warehouse has been captured by another coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] OnAfterAirbaseCaptured + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param DCS#coalition.side Coalition which captured the airbase. + --- Triggers the FSM event "AirbaseRecaptured" when the airbase of the warehouse has been re-captured from the other coalition. -- @param #WAREHOUSE self @@ -829,6 +880,14 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. -- @param DCS#coalition.side Coalition which re-captured the airbase. + --- On after "AirbaseRecaptured" event user function. Called when the airbase of the warehouse has been re-captured from the other coalition. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] OnAfterAirbaseRecaptured + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param DCS#coalition.side Coalition which re-captured the airbase. + --- Triggers the FSM event "Destroyed" when the warehouse was destroyed. All services are stopped. -- @param #WAREHOUSE self @@ -839,6 +898,13 @@ function WAREHOUSE:New(warehouse, alias) -- @function [parent=#WAREHOUSE] Destroyed -- @param #number delay Delay in seconds. + --- On after "Destroyed" event user function. Called when the warehouse was destroyed. All services are stopped. + -- @param #WAREHOUSE self + -- @function [parent=#WAREHOUSE] OnAfterDestroyed + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + return self end @@ -1117,7 +1183,7 @@ function WAREHOUSE:HasConnectionNaval(warehouse, markpath, smokepath) if shippinglane then return true,1 else - env.info("FF no shipping lane!") + self:_ErrorMessage("No shipping lane!") end end @@ -1143,6 +1209,14 @@ function WAREHOUSE:GetNumberOfAssets(Descriptor, DescriptorValue) end +--- Get assignment of a request. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Pendingitem request The request from which the assignment is extracted. +-- @return #string The assignment text. +function WAREHOUSE:GetAssignment(request) + return request.assignment +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1236,7 +1310,7 @@ end -- @param #string Event Event. -- @param #string To To state. function WAREHOUSE:onafterStop(From, Event, To) - self:I(self.wid..string.format("Warehouse %s stopped!", self.alias)) + self:_InfoMessage(string.format("Warehouse %s stopped!", self.alias)) -- Unhandle event. self:UnHandleEvent(EVENTS.Birth) @@ -1341,10 +1415,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu if type(group)=="string" then group=GROUP:FindByName(group) end - - -- Debug info. - self:I(self.wid..string.format("Adding %d assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias)) - + if group then -- Get unique ids from group name. @@ -1358,15 +1429,19 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Note the group is only added once, i.e. the ngroups parameter is ignored here. -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. - if asset~=nil then - self:E(string.format("Adding new asset with id = %d, attribute = %s to warehouse stock.", asset.uid, asset.attribute)) + if asset~=nil then + self:_DebugMessage(string.format("Adding known asset uid=%d, attribute = %s to warehouse stock.", asset.uid, asset.attribute), 5) table.insert(self.stock, asset) else - env.error("ERROR known asset could not be found in global warehouse db!") + self:_ErrorMessage(string.format("ERROR known asset could not be found in global warehouse db!"), 0) end else + -- Debug info. + self:_Debugmessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) + + -- This is a group that is not in the db yet. Add it n times. local assets=self:_RegisterAsset(group, n, forceattribute) @@ -1380,7 +1455,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- TODO: This causes a problem, when a completely new asset is added, i.e. not from a template group. -- Need to create a "zombie" template group maybe? if group:IsAlive()==true then - self:E(self.wid..string.format("Destroying group %s.", group:GetName())) + self:_DebugMessage(string.format("Destroying group %s.", group:GetName()), 5) group:Destroy(true) end @@ -1402,12 +1477,12 @@ function WAREHOUSE:_FindAssetInDB(group) local asset=WAREHOUSE.db.Assets[aid] self:E({asset=asset}) if asset==nil then - self:E(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName())) + self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName()), 0) end return asset end - self:E(string.format("ERROR: Group %s does not contain an asset ID in its name!", group:GetName())) + self:_ErrorMessage(string.format("ERROR: Group %s does not contain an asset ID in its name!", group:GetName()), 0) return nil end @@ -1660,7 +1735,7 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) if #parking<#template.units then local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) - self:E(text) + self:_DebugMessage(text) return nil end @@ -1689,7 +1764,9 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) local coord=parking[i].Coordinate --Core.Point#COORDINATE local terminal=parking[i].TerminalID --#number - coord:MarkToAll(string.format("Spawnplace unit %s terminal %d", unit.name, terminal)) + if self.Debug then + coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)) + end unit.x=coord.x unit.y=coord.z @@ -1805,7 +1882,7 @@ function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescripto end end if not gotit then - self:E(self.wid.."ERROR: Invalid request. Asset attribute is unknown!") + self:_ErrorMessage("ERROR: Invalid request. Asset attribute is unknown!", 5) okay=false end @@ -1819,26 +1896,26 @@ function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescripto end end if not gotit then - self:E(self.wid.."ERROR: Invalid request. Asset category is unknown!") + self:_ErrorMessage("ERROR: Invalid request. Asset category is unknown!", 5) okay=false end elseif AssetDescriptor==WAREHOUSE.Descriptor.TEMPLATENAME then if type(AssetDescriptorValue)~="string" then - self:E(self.wid.."ERROR: Invalid request. Asset template name must be passed as a string!") + self:_ErrorMessage("ERROR: Invalid request. Asset template name must be passed as a string!", 5) okay=false end elseif AssetDescriptor==WAREHOUSE.Descriptor.UNITTYPE then if type(AssetDescriptorValue)~="string" then - self:E(self.wid.."ERROR: Invalid request. Asset unit type must be passed as a string!") + self:_ErrorMessage("ERROR: Invalid request. Asset unit type must be passed as a string!", 5) okay=false end else - self:E(self.wid.."ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, TEMPLATENAME or UNITTYPE!") + self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, TEMPLATENAME or UNITTYPE!", 5) okay=false end @@ -1920,7 +1997,7 @@ end -- @param #WAREHOUSE.Queueitem Request Information table of the request. -- @return #boolean If true, request is granted. function WAREHOUSE:onbeforeRequest(From, Event, To, Request) - self:E({warehouse=self.alias, request=Request}) + self:T3({warehouse=self.alias, request=Request}) -- Distance from warehouse to requesting warehouse. local distance=self.coordinate:Get2DDistance(Request.warehouse.coordinate) @@ -1929,9 +2006,8 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) local _assets=Request.cargoassets if Request.nasset==0 then - local text=string.format("Request denied! Zero assets were requested.") - MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:E(self.wid..text) + local text=string.format("Warehouse %s: Request denied! Zero assets were requested.", self.alias) + self:_InfoMessage(text, 10) return false end @@ -1940,10 +2016,9 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) local asset=_asset --#WAREHOUSE.Assetitem -- Check if destination is in range. - if asset.range1 then @@ -2587,10 +2667,9 @@ function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Debug info local text=string.format("Warehouse %s: All assets delivered to warehouse %s!", self.alias, request.warehouse.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text, 5) - -- Make some noise :) + -- Make some noise :) self:_Fireworks(request.warehouse.coordinate) -- Remove pending request: @@ -2606,24 +2685,27 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. +-- @param Core.Set#SET_GROUP groupset The set of asset groups that was delivered to the warehouse itself. -- @param #WAREHOUSE.Pendingitem request Pending self request. function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) -- Debug info. - self:I(self.wid..string.format("Assets spawned at warehouse %s after self request!", self.alias)) + self:_DebugMessage(string.format("Assets spawned at warehouse %s after self request!", self.alias)) -- Debug info. for _,_group in pairs(groupset:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP - local text=string.format("Group name = %s, IsAlive=%s.", tostring(group:GetName()), tostring(group:IsAlive())) - env.info(text) - --group:SmokeGreen() + + --local text=string.format("Group name = %s, IsAlive=%s.", tostring(group:GetName()), tostring(group:IsAlive())) + --env.info(text) + + if self.Debug then + group:FlareGreen() + end end -- Add a "defender request" to be able to despawn all assets once defeated. - if self:IsAttacked() then - --self.defenderrequest=request + if self:IsAttacked() then table.insert(self.defending, request) end @@ -2642,8 +2724,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) -- Warning. local text=string.format("Warehouse %s: We are under attack!", self.alias) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Debug smoke. if self.Debug then @@ -2653,16 +2734,17 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) -- Spawn all ground units in the spawnzone? if self.autodefence then local nground=self:GetNumberOfAssets(WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND) - local text=string.format("Warehouse auto defence activated. Deploying all %d ground assets.", nground) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + local text=string.format("Warehouse auto defence activated.\n") + if nground>0 then + text=text..string.format("Deploying all %d ground assets.", nground) + + -- Add self request. self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) else - local text=string.format("No ground assets currently available.") - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + text=text..string.format("No ground assets currently available.") end + self:_InfoMessage(text) else local text=string.format("Warehouse auto defence inactive.") self:I(self.wid..text) @@ -2678,8 +2760,7 @@ function WAREHOUSE:onafterDefeated(From, Event, To) -- Message. local text=string.format("Warehouse %s: Enemy attack was defeated!", self.alias) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Debug smoke. if self.Debug then @@ -2703,8 +2784,7 @@ function WAREHOUSE:onafterDefeated(From, Event, To) -- Add asset group back to stock after 60 seconds. self:__AddAsset(60, group) end - - --self:_DeleteQueueItem(request, self.defending) + end self.defending=nil @@ -2723,8 +2803,7 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) -- Message. local text=string.format("Warehouse %s: We were captured by enemy coalition (%d)!", self.alias, Coalition) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Respawn warehouse with new coalition/country. self.warehouse:ReSpawn(Country) @@ -2772,8 +2851,7 @@ function WAREHOUSE:onafterAirbaseCaptured(From, Event, To, Coalition) -- Message. local text=string.format("Warehouse %s: Our airbase %s was captured by the enemy (coalition=%d)!", self.alias, self.airbasename, Coalition) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Debug smoke. if self.Debug then @@ -2799,8 +2877,7 @@ function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) -- Message. local text=string.format("Warehouse %s: We recaptured our airbase %s from the enemy (coalition=%d)!", self.alias, self.airbasename, Coalition) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Set airbase and category. self.airbase=AIRBASE:FindByName(self.airbasename) @@ -2827,8 +2904,7 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) -- Message. local text=string.format("Warehouse %s was destroyed!", self.alias) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Stop warehouse FSM. self:Stop() @@ -2999,7 +3075,7 @@ end -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that arrived. function WAREHOUSE:_ArrivedSimple(group) - env.info(string.format("Group %s arrived (simple)!", tostring(group:GetName()))) + self:_DebugMessage(string.format("Group %s arrived (simple)!", tostring(group:GetName()))) if group then --Trigger "Arrived event. @@ -3042,8 +3118,7 @@ function WAREHOUSE:_OnEventArrived(EventData) -- Debug info. local text=string.format("Air asset group %s arrived at warehouse %s.", group:GetName(), self.alias) - MESSAGE:New(text, 20):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text) -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since @@ -3073,7 +3148,7 @@ function WAREHOUSE:_OnEventBirth(EventData) -- Note: Remember, group:IsAlive might(?) not return true here. local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:T(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s.", self.alias, EventData.IniUnitName)) else --self:T3({wid=wid, uid=self.uid, match=(wid==self.uid), tw=type(wid), tu=type(self.uid)}) end @@ -3090,7 +3165,7 @@ function WAREHOUSE:_OnEventEngineStartup(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:I(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:T(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) end end end @@ -3105,7 +3180,7 @@ function WAREHOUSE:_OnEventTakeOff(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:I(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:T(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) end end end @@ -3120,7 +3195,7 @@ function WAREHOUSE:_OnEventLanding(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:I(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) -- Get request of this group local request=self:_GetRequestOfGroup(group,self.pending) @@ -3132,9 +3207,13 @@ function WAREHOUSE:_OnEventLanding(EventData) -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. if group:GetCategory()==Group.Category.HELICOPTER then if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then - group:SmokeWhite() - self:__AddAsset(30, group) - end + + self:_DebugMessage("Helicopter landed in spawn zone. No pending request. Putting back into stock.") + if self.Debug then + group:SmokeWhite() + end + self:__AddAsset(30, group) + end end end @@ -3153,7 +3232,7 @@ function WAREHOUSE:_OnEventEngineShutdown(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:T(self.wid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.", self.alias, EventData.IniUnitName)) end end end @@ -3169,7 +3248,9 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) -- Check if warehouse was destroyed. local warehousename=self.warehouse:GetName() if EventData.IniUnitName==warehousename then - env.info(self.wid..string.format("Warehouse %s alias %s was destroyed!", warehousename, self.alias)) + self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!", warehousename, self.alias)) + + -- Trigger Destroyed event. self:Destroyed() end end @@ -3179,7 +3260,7 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) local group=EventData.IniGroup local wid,aid,rid=self:_GetIDsFromGroup(group) if wid==self.uid then - self:E(self.wid..string.format("Warehouse %s captured event dead or crash of its asset unit %s.", self.alias, EventData.IniUnitName)) + self:T(self.wid..string.format("Warehouse %s captured event dead or crash of its asset unit %s.", self.alias, EventData.IniUnitName)) end end @@ -3209,7 +3290,7 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) local NewCoalitionAirbase=airbase:GetCoalition() -- Debug info - self:I(self.wid..string.format("Airbase of warehouse %s (coalition = %d) was captured! New owner coalition = %d.",self.alias, self.coalition, NewCoalitionAirbase)) + self:T(self.wid..string.format("Airbase of warehouse %s (coalition = %d) was captured! New owner coalition = %d.",self.alias, self.coalition, NewCoalitionAirbase)) -- So what can happen? -- Warehouse is blue, airbase is blue and belongs to warehouse and red captures it ==> self.airbase=nil @@ -3655,8 +3736,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check if receiving warehouse is running. We do allow self requests if the warehouse is under attack though! if (not request.warehouse:IsRunning()) and (not request.toself and self:IsAttacked()) then local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.", self.alias, request.warehouse.alias, request.warehouse:GetState()) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text, 5) return false end @@ -3667,8 +3747,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check if enough assets are in stock. if not _enough then local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text, 5) return false end @@ -3688,8 +3767,7 @@ function WAREHOUSE:_CheckRequestNow(request) --if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then if Parking==nil then local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text, 5) return false end @@ -3721,8 +3799,7 @@ function WAREHOUSE:_CheckRequestNow(request) local Parking=self:_FindParkingForAssets(self.airbase,_transports) if Parking==nil then local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all transports at the moment.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text, 5) return false end @@ -3737,8 +3814,7 @@ function WAREHOUSE:_CheckRequestNow(request) -- Not enough or the right transport carriers. local text=string.format("Warehouse %s: Request denied! Not enough transport carriers available at the moment.", self.alias) - MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, self.Report or self.Debug) - self:I(self.wid..text) + self:_InfoMessage(text, 5) return false end @@ -3877,7 +3953,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) text=text..string.format("Total cargo bay capacity = %.1f kg\n", totalcargobay) text=text..string.format("Total cargo weight = %.1f kg\n", totalcargoweight) text=text..string.format("Minimum number of runs = %.1f", totalcargoweight/totalcargobay) - self:I(self.wid..text) + self:_DebugMessage(text) return used_transports end @@ -3920,7 +3996,7 @@ function WAREHOUSE:_CheckQueue() -- Delete invalid requests. for _,_request in pairs(invalid) do - self:E(self.wid..string.format("Deleting invalid request id=%d.",_request.uid)) + self:T(self.wid..string.format("Deleting invalid request id=%d.",_request.uid)) self:_DeleteQueueItem(_request, self.queue) end @@ -4116,7 +4192,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Add parkingspot for this asset unit. table.insert(parking[_asset.uid], parkingspot) - self:E(self.wid..string.format("Parking spot #%d is free for asset id=%d!", _termid, _asset.uid)) + self:T(self.wid..string.format("Parking spot #%d is free for asset id=%d!", _termid, _asset.uid)) -- Add the unit as obstacle so that this spot will not be available for the next unit. -- TODO Alternatively, I could remove this parking spot from the table, right? @@ -4125,10 +4201,12 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) gotit=true break else - self:E(self.wid..string.format("Parking spot #%d is occupied or not big enough!", _termid)) - local coord=problem.coord --Core.Point#COORDINATE - local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) - coord:MarkToAll(string.format(text)) + self:T(self.wid..string.format("Parking spot #%d is occupied or not big enough!", _termid)) + if self.Debug then + local coord=problem.coord --Core.Point#COORDINATE + local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) + coord:MarkToAll(string.format(text)) + end end end -- check terminal type @@ -4136,7 +4214,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) if not gotit then - self:E(self.wid..string.format("WARNING: No free parking spot for asset id=%d",_asset.uid)) + self:T(self.wid..string.format("WARNING: No free parking spot for asset id=%d",_asset.uid)) return nil end end -- loop over asset units @@ -4541,7 +4619,7 @@ function WAREHOUSE:_DisplayStatus() text=text..string.format("Pending requests = %d\n", #self.pending) text=text..string.format("------------------------------------------------------\n") text=text..self:_GetStockAssetsText() - env.info(text) + self:T(text) --TODO: number of ground, air, naval assets. end @@ -4636,7 +4714,45 @@ function WAREHOUSE:_Fireworks(coord) end end ---- Make a flight plan from a departure to a destination airport. +--- Info Message. +-- @param #WAREHOUSE self +-- @param #string text The text of the error message. +-- @param #number duration Message display duration in seconds. Default 20 sec. +function WAREHOUSE:_InfoMessage(text, duration) + duration=duration or 20 + if duration>0 then + MESSAGE:New(text, duration):ToCoalitionIf(self.coalition, self.Debug or self.Report) + end + self:I(self.wid..text) +end + + +--- Debug message. +-- @param #WAREHOUSE self +-- @param #string text The text of the error message. +-- @param #number duration Message display duration in seconds. Default 20 sec. +function WAREHOUSE:_DebugMessage(text, duration) + duration=duration or 20 + if duration>0 then + MESSAGE:New(text, duration):ToAllIf(self.Debug) + end + self:T(self.wid..text) +end + +--- Error message. +-- @param #WAREHOUSE self +-- @param #string text The text of the error message. +-- @param #number duration Message display duration in seconds. Default 20 sec. +function WAREHOUSE:_ErrorMessage(text, duration) + duration=duration or 20 + if duration>0 then + MESSAGE:New(text, duration):ToAllIf(self.Debug) + end + self:E(self.wid..text) +end + + +--- Calculate the maximum height an aircraft can reach for the given parameters. -- @param #WAREHOUSE self -- @param #number D Total distance in meters from Departure to holding point at destination. -- @param #number alphaC Climb angle in rad. @@ -4644,7 +4760,8 @@ end -- @param #number Hdep AGL altitude of departure point. -- @param #number Hdest AGL altitude of destination point. -- @param #number Deltahhold Relative altitude of holding point above destination. -function WAREHOUSE:_MakeFlightplan(D, alphaC, alphaD, Hdep, Hdest, Deltahhold) +-- @return #number Maximum height the aircraft can reach. +function WAREHOUSE:_GetMaxHeight(D, alphaC, alphaD, Hdep, Hdest, Deltahhold) local Hhold=Hdest+Deltahhold local hdest=Hdest-Hdep @@ -4825,7 +4942,7 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) h_holding=UTILS.Randomize(h_holding, 0.2) -- Max holding altitude. - local DeltaholdingMax=self:_MakeFlightplan(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0) + local DeltaholdingMax=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, 0) if h_holding>DeltaholdingMax then h_holding=math.abs(DeltaholdingMax) @@ -4839,7 +4956,7 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) --------------------------- -- Get max flight altitude relative to H_departure. - local h_max=self:_MakeFlightplan(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding) + local h_max=self:_GetMaxHeight(d_total, AlphaClimb, AlphaDescent, H_departure, H_holding, h_holding) -- Max flight level ASL aircraft can reach for given angles and distance. local FLmax = h_max+H_departure @@ -4921,6 +5038,53 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) c[#c+1]=Pdeparture wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure") + --- Begin of Cruise + local Pcruise=Pdeparture:Translate(d_climb, heading) + Pcruise.y=FLcruise + c[#c+1]=Pcruise + wp[#wp+1]=Pcruise:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxCruise, true, nil, nil, "Cruise") + + --- Descent + local Pdescent=Pcruise:Translate(d_cruise, heading) + Pdescent.y=FLcruise + c[#c+1]=Pdescent + wp[#wp+1]=Pdescent:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxDescent, true, nil, nil, "Descent") + + --- Holding point + Pholding.y=H_holding+h_holding + c[#c+1]=Pholding + wp[#wp+1]=Pholding:WaypointAir("BARO", COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, VxHolding, true, nil, nil, "Holding") + + --- Final destination. + c[#c+1]=Pdestination + wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination") + + + -- Mark points at waypoints for debugging. + if self.Debug then + for i,coord in pairs(c) do + local coord=coord --Core.Point#COORDINATE + local dist=0 + if i>1 then + dist=coord:Get2DDistance(c[i-1]) + end + coord:MarkToAll(string.format("Waypoint %i, distance = %.2f km",i, dist/1000)) + end + end + + return wp,c +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--[[ + --- Departure/Take-off + c[#c+1]=Pdeparture + wp[#wp+1]=Pdeparture:WaypointAir("RADIO", COORDINATE.WaypointType.TakeOffParking, COORDINATE.WaypointAction.FromParkingArea, VxClimb, true, departure, nil, "Departure") + --- Climb local Pclimb=Pdeparture:Translate(d_climb/2, heading) Pclimb.y=H_departure+(FLcruise-H_departure)/2 @@ -4952,27 +5116,5 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) --- Final destination. c[#c+1]=Pdestination - wp[#wp+1]=Pcruise2:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination") - - - -- Mark points at waypoints for debugging. - if self.Debug then - for i,coord in pairs(c) do - local coord=coord --Core.Point#COORDINATE - env.info(i) - local dist=0 - if i>1 then - dist=coord:Get2DDistance(c[i-1]) - end - coord:MarkToAll(string.format("Waypoint %i, dist = %.2f km",i, dist/1000)) - end - end - - return wp,c -end - - -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - + wp[#wp+1]=Pdestination:WaypointAir("RADIO", COORDINATE.WaypointType.Land, COORDINATE.WaypointAction.Landing, VxFinal, true, destination, nil, "Final Destination") +]] From 13451ed6023586d781072cbe4f72aa98afb89cb7 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 3 Sep 2018 21:51:38 +0200 Subject: [PATCH 43/73] Warehosue v0.3.4 Fixed some bugs. Helos on ships are now not spawned in uncontrolled state due to DCS bug. Self requests are not deleted from the pending queue any more in case they return to their warehouse. --- Moose Development/Moose/AI/AI_Formation.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 3 +- .../Moose/Functional/Warehouse.lua | 40 +++++++++++++------ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index 8be01bd23..489e69cf3 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -650,7 +650,7 @@ function AI_FORMATION:onafterFormationLine( FollowGroupSet, From , Event , To, X local FollowSet = FollowGroupSet:GetSet() - local i = 0 + local i = 1 --FF i=0 caused first unit to have no XSpace! Probably needs further adjustments. This is just a quick work around. for FollowID, FollowGroup in pairs( FollowSet ) do diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 1ab9d9f78..1adc3f820 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1118,7 +1118,8 @@ function ZONE_UNIT:GetRandomVec2() self:F( self.ZoneName ) local RandomVec2 = {} - local Vec2 = self.ZoneUNIT:GetVec2() + --local Vec2 = self.ZoneUNIT:GetVec2() -- FF: This does not take care of the new offset feature! + local Vec2 = self:GetVec2() if not Vec2 then Vec2 = self.LastVec2 diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 42feeedc6..f25c16ab8 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -458,7 +458,7 @@ WAREHOUSE.Attribute = { NAVAL_ARMEDSHIP="Naval_ArmedShip", NAVAL_UNARMEDSHIP="Naval_UnarmedShip", NAVAL_OTHER="Naval_OtherNaval", - UNKNOWN="Unknown", + UNKNOWN="Other_Unknown", } --- Cargo transport type. Defines how assets are transported to their destination. @@ -489,7 +489,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.3" +WAREHOUSE.version="0.3.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1177,7 +1177,13 @@ end function WAREHOUSE:HasConnectionNaval(warehouse, markpath, smokepath) if warehouse then - + + -- Self request + if warehouse.warehouse:GetName()==self.warehouse:GetName() then + return true,1 + end + + -- Get shipping lane. local shippinglane=self.shippinglanes[warehouse.warehouse:GetName()] if shippinglane then @@ -1354,6 +1360,9 @@ end function WAREHOUSE:onafterStatus(From, Event, To) self:I(self.wid..string.format("Checking status of warehouse %s. Current FSM state %s. Global warehouse assets = %d.", self.alias, self:GetState(), #WAREHOUSE.db.Assets)) + -- Update coordinate in case we have a "moving" warehouse (e.g. on a carrier). + self.coordinate=self.warehouse:GetCoordinate() + -- Print status. self:_DisplayStatus() @@ -1439,7 +1448,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu else -- Debug info. - self:_Debugmessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) + self:_DebugMessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) -- This is a group that is not in the db yet. Add it n times. @@ -1640,6 +1649,8 @@ function WAREHOUSE:_SpawnAssetGroundNaval(asset, request, spawnzone, aioff) -- Get a random coordinate in the spawn zone. local coord=spawnzone:GetRandomCoordinate() + + --spawnzone:SmokeZone(1, 30) -- Translate the position of the units. for i=1,#template.units do @@ -1690,8 +1701,9 @@ end -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. -- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. +-- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) +function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled, hotstart) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then @@ -1705,8 +1717,6 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) else - - local hotstart=true -- Cold start (default). local _type=COORDINATE.WaypointType.TakeOffParking @@ -1782,6 +1792,12 @@ function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled) template.x = template.units[1].x template.y = template.units[1].y + -- DCS bug workaround. Spawning helos in uncontrolled state on carriers causes a big spash! + -- See https://forums.eagle.ru/showthread.php?t=219550 + if AirbaseCategory == Airbase.Category.SHIP and asset.category==Group.Category.HELICOPTER then + uncontrolled=false + end + -- Uncontrolled spawning. template.uncontrolled=uncontrolled @@ -2710,7 +2726,7 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) end -- Remove pending request. - self:_DeleteQueueItem(request, self.pending) + --self:_DeleteQueueItem(request, self.pending) end --- On after "Attacked" event. Warehouse is under attack by an another coalition. @@ -3468,7 +3484,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) local request=_request --#WAREHOUSE.Queueitem -- Debug info. - self:T2(self.wid..string.format("Checking request = %d.", request.uid)) + self:T2(self.wid..string.format("Checking request id=%d.", request.uid)) -- Let's assume everything is fine. local valid=true @@ -4632,12 +4648,10 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) -- Get assets in stock. local _data=self:GetStockInfo(self.stock) - --[[ local function _sort(a,b) - return a Date: Tue, 4 Sep 2018 16:23:24 +0200 Subject: [PATCH 44/73] Warehouse v0.3.4w --- .../Moose/Functional/Warehouse.lua | 431 ++++++++++++++++-- 1 file changed, 404 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index f25c16ab8..d4db49502 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -15,7 +15,7 @@ -- -- === -- --- ### Authors: **funkyfranky** +-- ### Authors: **funkyfranky**, FlightControl (cargo dispatcher classes) -- -- @module Functional.Warehouse -- @image Warehouse.JPG @@ -45,6 +45,7 @@ -- @field #table stock Table holding all assets in stock. Table entries are of type @{#WAREHOUSE.Assetitem}. -- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. -- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table elements are of type @{#WAREHOUSE.Pendingitem}. +-- @field #table delivered Table holding all delivered requests. -- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @field Core.Zone#ZONE portzone Zone defining the port of a warehouse. This is where naval assets are spawned. -- @field #table shippinglanes Table holding the user defined shipping between warehouses. @@ -77,7 +78,8 @@ -- by themselfs. Once arrived at the requesting warehouse, the assets go into the stock of the requestor and can be activated/deployed when necessary. -- -- ## What assets can be stored? --- Any kind of ground, airborne or naval asset can be stored. +-- Any kind of ground, airborne or naval asset can be stored and are spawned upon request. +-- The fact that the assets "live" only virtually in the stock has a positive impact on the game performance. -- -- ## What means of transportation are available? -- Firstly, all mobile assets can be send from warehouse to another on their own. @@ -228,9 +230,13 @@ -- By default, the zone were ground assets are spawned is a circular zone around the physical location of the warehouse with a radius of 200 meters. However, the location of the -- spawn zone can be set by the @{#WAREHOUSE.SetSpawnZone}(*zone*) functions. It is advisable to choose a zone which is clear of obstacles. -- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Batumi.png) +-- -- The parameter *zone* is a MOOSE @{Core.Zone#ZONE} object. So one can, e.g., use trigger zones defined in the mission editor. If a cicular zone is not desired, one -- can use a polygon zone (see @{Core.Zone#ZONE_POLYGON}). -- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_SpawnPolygon.png) +-- -- ## Road Connections -- -- Ground assets will use a road connection to travel from one warehouse to another. Therefore, a proper road connection is necessary. @@ -266,12 +272,16 @@ -- A port in this context is the zone where all naval assets are spawned. This zone can be defined with the function @{#WAREHOUSE.SetPortZone}(*zone*), where the parameter -- *zone* is a MOOSE zone. So again, this can be create from a trigger zone defined in the mission editor or if a general shape is desired by a @{Core.Zone#ZONE_POLYGON}. -- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_PortZone.png) +-- -- ### Defining Shipping Lanes -- -- A shipping lane between to warehouses can be defined by the @{#WAREHOUSE.AddShippingLane}(*remotewarehouse*, *group*) function. The first parameter *remotewarehouse* -- is the warehouse which should be connected to the present warehouse. -- --- The parameter *group* should be a late activated group defined in the mission editor. The waypoints of this group are used as waypoints of the shipping lane. +-- The parameter *group* should be a late activated group defined in the mission editor. The waypoints of this group are used as waypoints of the shipping lane. +-- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_ShippingLane.png) -- -- -- # Strategic Considerations @@ -317,8 +327,347 @@ -- === -- -- # Examples +-- +-- ## Example 1: Self Request +-- +-- Ground troops are taken from the Batumi warehouse stock and spawned in its spawn zone. After a short delay, they are added back to the warehouse stock. +-- Also a new request is made. Hence, the groups will be spawned, added back to the warehouse, spawned again and so on and so forth... +-- +-- -- Start warehouse Batumi. +-- warehouse.Batumi:Start() +-- +-- -- Add five groups of infantry as assets. +-- warehouse.Batumi:AddAsset(GROUP:FindByName("Infantry Platoon Alpha"), 5) +-- +-- -- Add self request for three infantry at Batumi. +-- warehouse.Batumi:AddRequest(warehouse.Batumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 3) +-- +-- +-- --- Self request event. Triggered once the assets are spawned in the spawn zone or at the airbase. +-- function warehouse.Batumi:OnAfterSelfRequest(From, Event, To, groupset, request) +-- local mygroupset=groupset --Core.Set#SET_GROUP +-- +-- -- Loop over all groups spawned from that request. +-- for _,group in pairs(mygroupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP +-- +-- -- Gree smoke on spawned group. +-- group:SmokeGreen() +-- group:FlareRed() +-- +-- -- Put asset back to stock after 10 seconds. +-- warehouse.Batumi:__AddAsset(10, group) +-- end +-- +-- -- Add new self request after 20 seconds. +-- warehouse.Batumi:__AddRequest(20, warehouse.Batumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 3) +-- +-- end -- --- **WIP** +-- ## Example 2: Self propelled Ground Troops +-- +-- Warehouse Berlin, which is a FARP near Batumi, requests infantry and troop transports from the warehouse at Batumi. +-- The groups are spawned at Batumi and move by themselfs from Batumi to Berlin using the roads. +-- Once the troops have arrived at Berlin, the troops are automatically added to the warehouse stock of Berlin. +-- While on the road, Batumi has requested back two APCs from Berlin. Since Berlin does not have the assets in stock, +-- the request is queued. After the troops have arrived, Berlin is sending back the APCs to Batumi. +-- +-- -- Start Warehouse at Batumi. +-- warehouse.Batumi:Start() +-- +-- -- Add 20 infantry groups as assets at Batumi. +-- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) +-- warehouse.Batumi:AddAsset("TPz Fuchs", 5) +-- +-- -- Start Warehouse Berlin. +-- warehouse.Berlin:Start() +-- +-- -- Warehouse Berlin requests 10 infantry groups and 3 APCs from warehouse Batumi. +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 10) +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_APC, 5) +-- +-- -- Request from Batumi for 2 APCs. Initially these are not in stock. When they become available, the request is executed. +-- warehouse.Berlin:AddRequest(warehouse.Batumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_APC, 2) +-- +-- ## Example 3: Self Propelled Airborne Assets +-- +-- Warehouse Senaki receives requests from Kutaisi for one Yak-52s and from FARP London for three Hueys. +-- Assets are spawned in Senaki and make their way to the requesting warehouses. +-- Once the units have arrived they are added to the stock of the receiving warehouses and can be used for further assignments. +-- +-- -- Start sending warehouse. +-- warehouse.Senaki:Start() +-- +-- -- Add assets. +-- warehouse.Senaki:AddAsset("Yak-52", 10) +-- warehouse.Senaki:AddAsset("Huey", 10) +-- +-- -- Start receiving warehouses +-- warehouse.Kutaisi:Start() +-- warehouse.London:Start() +-- +-- -- Kusaisi requests one Yak-52 form Senaki. FARP London requests three UH-1H Huys from Senaki. +-- warehouse.Senaki:AddRequest(warehouse.Kutaisi, WAREHOUSE.Descriptor.TEMPLATENAME, "Yak-52", 1) +-- warehouse.Senaki:AddRequest(warehouse.London, WAREHOUSE.Descriptor.TEMPLATENAME, "Huey", 3) +-- +-- ## Example 4: Transport of Assets by APCs +-- +-- Warehouse at FARP Berlin requests three infantry groups from Batumi. These assets shall be transported using one APC. +-- Infantry and APC are spawned in the spawn zone at Batumi. The APC picks up two of the three infantry groups and +-- drives them to Berlin. There, they unboard and walk to the warehouse where they will be added to the stock. +-- Meanwhile the APC drives back and picks up the last infantry group and also brings it to Batumi. +-- The APC will then return to Batumi and be added back to the stock of the Batumi warehouse. +-- The reason that the APC has to drive twice, it that can only up to ten soldiers. +-- +-- -- Start Warehouse at Batumi. +-- warehouse.Batumi:Start() +-- +-- -- Start Warehouse Berlin. +-- warehouse.Berlin:Start() +-- +-- -- Add 20 infantry groups and five APCs as assets at Batumi. +-- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) +-- warehouse.Batumi:AddAsset("TPz Fuchs", 5) +-- +-- -- Warehouse Berlin requests 3 infantry groups from warehouse Batumi using 1 APC for transport. +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 3, WAREHOUSE.TransportType.APC, 1) +-- +--## Example 5: Transport of Assets by Helicopters +-- +-- Warehouse at FARP Berlin requests 10 infantry groups from Batumi. They shall be transported by one helicopter. +-- Note that the UH-1H Huey in DCS is an attack and not a transport helo. So the warehouse logic would be default also +-- register it as an @{#WAREHOUSE.Attribute.AIR_ATTACKHELICOPTER}. In order to use it as a transport we need to force +-- it to be added as transport helo. +-- +-- -- Start Warehouses. +-- warehouse.Batumi:Start() +-- warehouse.Berlin:Start() +-- +-- -- Add 20 infantry groups as assets at Batumi. +-- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) +-- +-- -- Add five Hueys for transport. Note that the Huey in DCS is an attack and not a transport helo. So we force the attribute! +-- warehouse.Batumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) +-- +-- -- Warehouse Berlin requests 10 infantry groups from warehouse Batumi using one huey for transport. +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 10, WAREHOUSE.TransportType.HELICOPTER, 1) +-- +--## Example 6: Transport of Assets by Airplanes +-- +-- Kutaisi requests 20 infantry groups from Senaki. These assets will be loaded into one C-130 cargo plane. +-- +-- -- Start Warehouses. +-- warehouse.Senaki:Start() +-- warehouse.Kutaisi:Start() +-- +-- -- Add 20 infantry groups and 5 C-130 transport planes as assets to Senaki warehouse. +-- warehouse.Senaki:AddAsset("Infantry Platoon Alpha", 20) +-- warehouse.Senaki:AddAsset("C-130", 5) +-- +-- -- Warehouse Berlin requests 10 infantry groups from warehouse Batumi using 3 APCs for transport. +-- warehouse.Senaki:AddRequest(warehouse.Kutaisi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 20, WAREHOUSE.TransportType.AIRPLANE, 1) +-- +-- ## Example 7: Capturing Airbase and Warehouse +-- +-- A red BMP has made it through our defence lines and drives towards our unprotected airbase at Senaki. +-- Once the BMP captures the airbase (DCS S\_EVENT_\BASE_\CAPTURED is evaluated) the warehouse at Senaki lost its air infrastructure and it is not +-- possible any more to spawn airborne units. All requests for airborne units are rejected and not queued in this case. +-- +-- The red BMP then drives further to the warehouse. Once it enters the warehouse zone (500 m radius around the warehouse building), the warehouse is +-- considered to be under attack. This triggers the event **Attacked**. The @{#WAREHOUSE.OnAfterAttacked} function can be used to react to this situation. +-- Here, we only broadcast a distress call and launch a flare. However, it would also be reasonable to spawn all or selected ground troops in order to defend +-- the warehouse. Note, that the warehouse has a self defence option which can be activated via the @{#WAREHOUSE.SetAutoDefenceOn}() function. If activated, +-- *all* ground assets are automatically spawned and assigned to defend the warehouse. Once/if the attack is defeated, these assets go automatically back +-- into the warehouse stock. +-- +-- If the red coalition manages to capture our warehouse, all assets go into their possession. Here, even our airbase has been captured. Therefore, a (self) request +-- to the warehouse will now spawn the F/A-18 fighters as red units. Note, that the request could also some from another red warehouse. In that case, +-- the planes would take off and (if they make it) be added to the red warehouse. So you can steal valuable assets from your enemy if he is not careful. +-- +-- Here, we simply activate a blue external unit which drives to the warehouse, destroyes the red intruder and re-captures our warehouse. +-- +-- -- Start warehouse. +-- warehouse.Senaki:Start() +-- +-- -- Add some assets. +-- warehouse.Senaki:AddAsset("TPz Fuchs", 5) +-- warehouse.Senaki:AddAsset("Infantry Platoon Alpha", 10) +-- warehouse.Senaki:AddAsset("F/A-18C 2ship", 10) +-- +-- -- Auto defence! When enabled, all ground troops of the warehouse are spawned automatically to defend the warehouse. +-- -- warehouse.Senaki:SetAutoDefenceOn() +-- +-- -- Red BMP trying to capture the airfield and later the warehouse. +-- local red1=GROUP:FindByName("Red BMP-80 Senaki") +-- red1:Activate() +-- +-- -- The red BMP first drives to the airbase which gets captured and changes from blue to red. So the warehouse loses its airbase. +-- function warehouse.Senaki:OnAfterAirbaseCaptured(From,Event,To,Coalition) +-- -- This request should not be processed since the warehouse has lost its airbase. In fact it is deleted from the queue. +-- warehouse.Senaki:AddRequest(warehouse.Senaki,WAREHOUSE.Descriptor.CATEGORY, Group.Category.AIRPLANE, 1) +-- end +-- +-- -- Enemy has entered the warehouse zone. This triggers the "Attacked" event. +-- function warehouse.Senaki:OnAfterAttacked(From,Event,To,Coalition,Country) +-- MESSAGE:New(string.format("Warehouse %s: We are under attack!", self.alias), 30):ToCoalition(self.coalition) +-- self.coordinate:SmokeRed() +-- end +-- +-- -- Now the red BMP also captured the warehouse. So the warehouse and the airbase are both red and planes can be spawned again. +-- function warehouse.Senaki:OnAfterCaptured(From,Event,To,Coalition,Country) +-- -- These units will be spawned as red units because the warehouse has just been captured. +-- warehouse.Senaki:AddRequest(warehouse.Senaki,WAREHOUSE.Descriptor.CATEGORY, Group.Category.AIRPLANE, 1) +-- +-- -- Activate Blue Humvee to recapture the warehouse. +-- local blue1=GROUP:FindByName("blue1") +-- blue1:Activate() +-- end +-- +-- ## Example 8: Destroying a Warehouse +-- +-- After 30 seconds into the mission we create and (artificial) big explosion - or a terrorist attack if you like - which completely destroys the +-- the warehouse at Batumi. All assets are gone and requests cannot be processed anymore. +-- +-- -- Start Batumi and Berlin warehouses. +-- warehouse.Batumi:Start() +-- warehouse.Berlin:Start() +-- +-- -- Add some assets. +-- warehouse.Batumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) +-- warehouse.Berlin:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) +-- +-- -- Big explosion at the warehose. It has a very nice damage model by the way :) +-- local function DestroyWarehouse() +-- warehouse.Batumi.warehouse:GetCoordinate():Explosion(9999) +-- end +-- +-- -- Create and explosion after 30 sec. +-- SCHEDULER:New(nil, DestroyWarehouse, {}, 30) +-- +-- -- These requests should not be processed any more since the warehouse is destroyed. +-- warehouse.Batumi:__AddRequest(35, warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_TRANSPORTHELO, 1) +-- warehouse.Berlin:__AddRequest(40, warehouse.Batumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_TRANSPORTHELO, 1) +-- +-- ## Example 9: Self Propelled Naval Assets +-- +-- Kobuleti requests a war ship from Batumi. Both warehouses need to have a port, which we define by two polygon zones at a place +-- in the sea closest to the warehouses. Also a shipping lane between the two warehouses needs to be defined manually. +-- With this infrastructure it is possible to exachange naval assets between warehouses. +-- +-- -- Start warehouses. +-- warehouse.Batumi:Start() +-- warehouse.Kobuleti:Start() +-- +-- -- Define ports and shipping lanes. +-- warehouse.Batumi:SetPortZone(ZONE_POLYGON:NewFromGroupName("Warehouse Batumi Port", "Warehouse Batumi Port")) +-- warehouse.Kobuleti:SetPortZone(ZONE_POLYGON:NewFromGroupName("Warehouse Kobuleti Port", "Warehouse Kobuleti Port")) +-- warehouse.Batumi:AddShippingLane(warehouse.Kobuleti, GROUP:FindByName("Warehouse Batumi-Kobuleti Shipping Lane")) +-- +-- -- Add five USS Normandy naval assets. +-- warehouse.Batumi:AddAsset("Normandy", 5) +-- +-- -- Kobuleti requests a war ship from Batumi. +-- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.NAVAL_WARSHIP) +-- +-- ## Example 10: Aircraft Carrier - Rescue Helo and Escort +-- +-- This example shows how to spawn assets from a warehouse located on an aircraft carrier. +-- +-- After 10 seconds we make a self request for a rescue helicopter. Note, that the @{#WAREHOUSE.AddRequest} function has a parameter which lets you +-- specify an "Assignment". This can be later used to identify the request and take the right actions. +-- +-- Once the request is processed, the @{#WAREHOUSE.OnafterSelfRequest} function is called. This is where we hook in and postprocess the spawned assets. +-- In particular, we use the @{AI.AI_Formation#AI_FORMATION} class to make some nice escorts for our carrier. +-- +-- When the resue helo is spawned, we can check that this is the correct asset and make the helo go into formation with the carrier. +-- Once the helo runs out of fuel, it will automatically return to the ship and land. For the warehouse, this means that the "cargo", i.e. the helicopter +-- has been delivered - assets can be delivered to other warehouses and to the same warehouse - hence a *self* request. +-- When that happens, the **Delivered** event is triggered and the @{#WAREHOUSE.OnAfterDelivered} function called. This can now be used to spawn +-- a fresh helo. Effectively, there we created an infinite, never ending loop. So a rescue helo will be up at all times. +-- +-- After 30 and 45 seconds requests for five groups of armed speedboats are made. These will be spawned in the port zone right behind the carrier. +-- The first five groups will go port of the carrier an form a left wing formation. The seconds groups will to the analogue on the starboard side. +-- +-- -- Start warehouse on USS Stennis. +-- warehouse.Stennis:Start() +-- +-- -- Add speedboat and helo assets. +-- warehouse.Stennis:AddAsset("Speedboat", 10) +-- warehouse.Stennis:AddAsset("CH-53E", 3) +-- +-- -- Define a "port" at the Stennis to be able to spawn Naval assets. This zone will move behind the Stennis. +-- local stenniszone=ZONE_UNIT:New("Spawnzone Stennis", UNIT:FindByName("Stennis"), 100, {rho=250, theta=180, relative_to_unit=true}) +-- warehouse.Stennis:SetPortZone(stenniszone) +-- +-- -- Self request of rescue helo and speed boats. +-- warehouse.Stennis:__AddRequest(10, warehouse.Stennis, WAREHOUSE.Descriptor.TEMPLATENAME, "CH-53E", 1, nil, nil, nil, "Rescue Helo") +-- warehouse.Stennis:__AddRequest(30, warehouse.Stennis, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.NAVAL_ARMEDSHIP, 5, nil, nil, nil, "Speedboats Left") +-- warehouse.Stennis:__AddRequest(45, warehouse.Stennis, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.NAVAL_ARMEDSHIP, 5, nil, nil, nil, "Speedboats Right") +-- +-- --- Function called after self request +-- function warehouse.Stennis:OnAfterSelfRequest(From,Event,To,groupset,request) +-- +-- local groupset=groupset --Core.Set#SET_GROUP +-- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem +-- +-- local Mother=UNIT:FindByName("Stennis") +-- +-- if request.assignment=="Speedboats Left" then +-- +-- -- Define AI Formation object. +-- -- Note that this has to be a global variable or the garbage collector will remove it for some reason! +-- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Left Formation with Carrier", "Follow Carrier at given parameters.") +-- +-- -- Formation parameters. +-- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50) +-- +-- CarrierFormationLeft:__Start(2) +-- +-- for _,group in pairs(groupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP +-- group:FlareRed() +-- end +-- +-- elseif request.assignment=="Speedboats Right" then +-- +-- -- Define AI Formation object. +-- -- Note that this has to be a global variable or the garbage collector will remove it for some reason! +-- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Right Formation with Carrier", "Follow Carrier at given parameters.") +-- +-- -- Formation parameters. +-- CarrierFormationRight:FormationRightWing(200 ,50, 0, 0, 500, 50) +-- +-- CarrierFormationRight:__Start(2) +-- +-- for _,group in pairs(groupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP +-- group:FlareGreen() +-- end +-- +-- elseif request.assignment=="Rescue Helo" then +-- +-- -- Define AI Formation object. +-- CarrierFormationHelo = AI_FORMATION:New(Mother, groupset, "Helo Formation with Carrier", "Follow Carrier at given parameters.") +-- +-- -- Formation parameters. +-- CarrierFormationHelo:FormationCenterWing(-150, 50, 20, 50, 100, 50) +-- +-- -- Start formation FSM. +-- CarrierFormationHelo:__Start(2) +-- +-- end +-- +-- --- When the helo is out of fuel, it will return to the carrier and should be delivered. +-- function warehouse.Stennis:OnAfterDelivered(From,Event,To,request) +-- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem +-- +-- -- So we start another request. +-- if request.assignment=="Rescue Helo" then +-- warehouse.Stennis:__AddRequest(10, warehouse.Stennis, WAREHOUSE.Descriptor.TEMPLATENAME, "CH-53E", 1, nil, nil, nil, "Rescue Helo") +-- end +-- end +-- +-- end -- -- -- @field #WAREHOUSE @@ -346,6 +695,7 @@ WAREHOUSE = { stock = {}, queue = {}, pending = {}, + delivered = {}, defending = {}, portzone = nil, shippinglanes = {}, @@ -489,33 +839,32 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.4" +WAREHOUSE.version="0.3.4w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? --- DONE: Add autoselfdefence switch and user function. Default should be off. --- DONE: Warehouse re-capturing not working?! --- DONE: Naval assets dont go back into stock once arrived. --- DONE: Take cargo weight into consideration, when selecting transport assets. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. --- DONE: Add ports for spawning naval assets. --- TODO: Added habours as interface for transport to from warehouses? --- DONE: Add shipping lanes between warehouses. +-- TODO: Added habours as interface for transport to from warehouses? -- TODO: Set ROE for spawned groups. -- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. -- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. --- DONE: Handle cases with immobile units <== should be handled by dispatcher classes. --- TODO: Handle cargo crates. --- DONE: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? --- TODO: Add general message function for sending to coaliton or debug. --- TODO: Fine tune event handlers. -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. --- DONE: Improve generalized attributes. -- TODO: Add a time stamp when an asset is added to the stock and for requests. +-- DONE: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? +-- DONE: Add autoselfdefence switch and user function. Default should be off. +-- DONE: Warehouse re-capturing not working?! +-- DONE: Naval assets dont go back into stock once arrived. +-- DONE: Take cargo weight into consideration, when selecting transport assets. +-- DONE: Add ports for spawning naval assets. +-- DONE: Add shipping lanes between warehouses. +-- DONE: Handle cases with immobile units <== should be handled by dispatcher classes. +-- DONE: Handle cases for aircraft carriers and other ships. Place warehouse on carrier possible? On others probably not - exclude them? +-- DONE: Add general message function for sending to coaliton or debug. +-- DONE: Fine tune event handlers. +-- DONE: Improve generalized attributes. -- DONE: If warehouse is destoyed, all asssets are gone. -- DONE: Add event handlers. -- DONE: Add AI_CARGO_AIRPLANE @@ -1087,12 +1436,14 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group) end -- Debug info. Marks along shipping lane. - for i=1,#lane do - local coord=lane[i] --Core.Point#COORDINATE - local text=string.format("Shipping lane %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) - coord:MarkToCoalition(text, self.coalition) + if self.Debug then + for i=1,#lane do + local coord=lane[i] --Core.Point#COORDINATE + local text=string.format("Shipping lane %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) + coord:MarkToCoalition(text, self.coalition) + end end - + -- Add shipping lane. self.shippinglanes[remotewarehouse.warehouse:GetName()]=lane @@ -1306,7 +1657,7 @@ function WAREHOUSE:onafterStart(From, Event, To) self:HandleEvent(EVENTS.EngineShutdown, self._OnEventArrived) -- Start the status monitoring. - self:__Status(1) + self:__Status(-1) end @@ -1328,6 +1679,15 @@ function WAREHOUSE:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Dead) self:UnHandleEvent(EVENTS.BaseCaptured) + self.pending=nil + self.pending={} + + self.queue=nil + self.queue={} + + self.stock=nil + self.stock={} + -- Clear all pending schedules. self.CallScheduler:Clear() end @@ -2687,6 +3047,9 @@ function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Make some noise :) self:_Fireworks(request.warehouse.coordinate) + + -- Add table + self.delivered[request.uid]=true -- Remove pending request: self:_DeleteQueueItem(request, self.pending) @@ -2721,7 +3084,19 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) end -- Add a "defender request" to be able to despawn all assets once defeated. - if self:IsAttacked() then + if self:IsAttacked() then + + -- Route (mobile) ground troops to warehouse zone if they are not alreay there. + if self.autodefence then + for _,_group in pairs(groupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + local speedmax=group:GetSpeedMax() + if group:IsGround() and speedmax>1 and group:IsNotInZone(self.zone) then + group:RouteGroundTo(self.zone:GetRandomCoordinate(), 0.8*speedmax, "Off Road") + end + end + end + table.insert(self.defending, request) end @@ -4605,7 +4980,7 @@ function WAREHOUSE:_PrintQueue(queue, name) if qitem.airbase then airbasename=qitem.airbase:GetName() end - text=text..string.format("\nUID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, Nasssets=%s, Transport=%s, Ntransport=%d.", + text=text..string.format("\nUID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, #Assets=%s, Transport=%s, #Transport=%d.", qitem.uid, qitem.prio, qitem.warehouse.alias, airbasename, qitem.category, qitem.assetdesc,tostring(qitem.assetdescval), tostring(qitem.nasset), qitem.transporttype, qitem.ntransport) end if #queue==0 then @@ -4648,10 +5023,12 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) -- Get assets in stock. local _data=self:GetStockInfo(self.stock) + --[[ local function _sort(a,b) return a[1] Date: Wed, 5 Sep 2018 00:20:05 +0200 Subject: [PATCH 45/73] Warehouse v0.3.5 Improved queue output. --- .../Moose/Functional/Warehouse.lua | 205 +++++++++++++----- 1 file changed, 151 insertions(+), 54 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index d4db49502..9d328706b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -95,6 +95,8 @@ -- in a realistic way by using the corresponding cargo dispatcher classes, i.e. @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}, -- @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} and @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}. -- +-- === +-- -- # Creating a Warehouse -- -- A MOOSE warehouse must be represented in game by a phyical *static* object. For example, the mission editor already has warehouse as static object available. @@ -131,6 +133,7 @@ -- 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}. -- +-- === -- -- # Requesting Assets -- @@ -160,7 +163,7 @@ -- -- Also not that the above request is for five infantry units. So any group in stock that has the generalized attribute "INFANTRY" can be selected. -- --- ### Requesting a Specific Unit Type +-- ## Requesting a Specific Unit Type -- -- A more specific request could look like: -- @@ -169,7 +172,7 @@ -- Here, Kobuleti requests a specific unit type, in particular two groups of A-10Cs. Note that the spelling is important as it must exacly be the same as -- what one get's when using the DCS unit type. -- --- ### Requesting a Specifc Group +-- ## Requesting a Specifc Group -- -- An even more specific request would be: -- @@ -177,7 +180,7 @@ -- -- In this case three groups named "Group Name as in ME" are requested. So this explicitly request the groups named like that in the Mission Editor. -- --- ### Requesting a general category +-- ## Requesting a general category -- -- On the other hand, very general unspecifc requests can be made as -- @@ -208,8 +211,8 @@ -- -- @param #WAREHOUSE.Pendingitem request Pending self request. -- function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) -- --- for _,_group in pairs(groupset:GetSetObjects()) do --- local group=_group --Wrapper.Group#GROUP +-- for _,group in pairs(groupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP -- group:SmokeGreen() -- end -- @@ -220,6 +223,8 @@ -- -- Note that airborne groups are spawned in uncontrolled state and need to be activated first before they can start their assigned mission. -- +-- === +-- -- # Infrastructure -- -- A good infrastructure is important for a warehouse to be efficient. Therefore, the location of a warehouse should be chosen with care. @@ -283,6 +288,30 @@ -- -- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_ShippingLane.png) -- +-- === +-- +-- # Why is my request not processed? +-- +-- For each request, the warehouse class logic does a lot of consistancy and validation checks under the hood. +-- This means that sometimes a request is deemed to be *invalid* in which case they are deleted from the queue or considered to be valid but cannot be executed at this very moment. +-- +-- ## Invalid Requests +-- +-- Invalid request are requests which cannot be processes **ever** because there is some logical or physical argument for it. Or simply because that feature was not implemented (yet). +-- +-- * One warehuse requests airborne assets from another warehouse but at one (or even both) warehouses do not have an associated airbase. +-- +-- All invalid requests are removed from the warehouse queue! +-- +-- ## Temporarily Unprocessable Requests +-- +-- Temporarily unprocessable requests are possible in priciple, but cannot be processed at the given time the warehouse checks its queue. +-- +-- * No enough parking spaces are available for the requests assets but the airbase has enough parking spots in total so that this request is possible once other aircraft have taken off. +-- +-- Temporarily unprocessable requests are held in the queue. If at some point in time, the situation changes so that these requests can be processed, they are executed. +-- +-- === -- -- # Strategic Considerations -- @@ -328,6 +357,32 @@ -- -- # Examples -- +-- This section shows some examples how the WAREHOUSE class is used in practice. This is one of the best ways to explain things, in my opinion. +-- +-- But first, let me introduce a convenient way to define several warehouses in a table. This is absolutely *not* necessary but quite handy if you have +-- multiple WAREHOUSE objects in your mission. +-- +-- ## Example 0: Setting up a Warehouse Array +-- +-- If you have multiple warehouses, you can put them in a table. This makes it easier to access them or to loop over them. +-- +-- -- Define Warehouses. +-- local warehouse={} +-- warehouse.Senaki = WAREHOUSE:New(STATIC:FindByName("Warehouse Senaki"), "Senaki") --Functional.Warehouse#WAREHOUSE +-- warehouse.Batumi = WAREHOUSE:New(STATIC:FindByName("Warehouse Batumi"), "Batumi") --Functional.Warehouse#WAREHOUSE +-- warehouse.Kobuleti = WAREHOUSE:New(STATIC:FindByName("Warehouse Kobuleti"), "Kobuleti") --Functional.Warehouse#WAREHOUSE +-- warehouse.Kutaisi = WAREHOUSE:New(STATIC:FindByName("Warehouse Kutaisi"), "Kutaisi") --Functional.Warehouse#WAREHOUSE +-- warehouse.Berlin = WAREHOUSE:New(STATIC:FindByName("Warehouse Berlin"), "Berlin") --Functional.Warehouse#WAREHOUSE +-- warehouse.London = WAREHOUSE:New(STATIC:FindByName("Warehouse London"), "London") --Functional.Warehouse#WAREHOUSE +-- warehouse.Stennis = WAREHOUSE:New(STATIC:FindByName("Warehouse Stennis"), "Stennis") --Functional.Warehouse#WAREHOUSE +-- +-- Remarks: +-- +-- * I defined the array as local, i.e. local warehouse={}. This is personal preference and sometimes causes trouble with the lua garbage collection. You can also define it as a global array/table! +-- * The "--Functional.Warehouse#WAREHOUSE" at the end is only to have the LDT intellisense working correctly. If you don't use LDT (which you should!), it can be omitted. +-- +-- **NOTE** that all examples below need this bit or code at the beginning - or at least the warehouses which are used. +-- -- ## Example 1: Self Request -- -- Ground troops are taken from the Batumi warehouse stock and spawned in its spawn zone. After a short delay, they are added back to the warehouse stock. @@ -745,11 +800,12 @@ WAREHOUSE = { --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem --- @extends #WAREHOUSE.Queueitem +-- @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 #number ntransporthome Number of transports back home. +-- @extends #WAREHOUSE.Queueitem --- Descriptors enumerator describing the type of the asset. -- @type WAREHOUSE.Descriptor @@ -786,7 +842,7 @@ WAREHOUSE.Descriptor = { -- @field #string NAVAL_ARMEDSHIP Any armed ship that is not an aircraft carrier, a cruiser, destroyer, firgatte or corvette. -- @field #string NAVAL_UNARMEDSHIP Any unarmed naval vessel. -- @field #string NAVAL_OTHER Any naval unit that does not fall into any other naval category. --- @field #string UNKNOWN Anything that does not fall into any other category. +-- @field #string OTHER_UNKNOWN Anything that does not fall into any other category. WAREHOUSE.Attribute = { AIR_TRANSPORTPLANE="Air_TransportPlane", AIR_AWACS="Air_AWACS", @@ -808,16 +864,16 @@ WAREHOUSE.Attribute = { NAVAL_ARMEDSHIP="Naval_ArmedShip", NAVAL_UNARMEDSHIP="Naval_UnarmedShip", NAVAL_OTHER="Naval_OtherNaval", - UNKNOWN="Other_Unknown", + OTHER_UNKNOWN="Other_Unknown", } --- Cargo transport type. Defines how assets are transported to their destination. -- @type WAREHOUSE.TransportType --- @field #string AIRPLANE Transports are conducted by airplanes. --- @field #string HELICOPTER Transports are conducted by helicopters. +-- @field #string AIRPLANE Transports are carried out by airplanes. +-- @field #string HELICOPTER Transports are carried out by helicopters. -- @field #string APC Transports are conducted by APCs. --- @field #string SHIP Transports are conducted by ships. --- @field #string TRAIN Transports are conducted by trains. Not yet implemented. +-- @field #string SHIP Transports are conducted by ships. Not implemented yet. +-- @field #string TRAIN Transports are conducted by trains. Not implemented yet. Also trains are buggy in DCS. -- @field #string SELFPROPELLED Assets go to their destination by themselves. No transport carrier needed. WAREHOUSE.TransportType = { AIRPLANE = "Air_TransportPlane", @@ -839,7 +895,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.4w" +WAREHOUSE.version="0.3.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -851,7 +907,7 @@ WAREHOUSE.version="0.3.4w" -- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. -- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. --- TODO: Add save/load capability of warehouse <==> percistance after mission restart. +-- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult! -- TODO: Add a time stamp when an asset is added to the stock and for requests. -- DONE: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? -- DONE: Add autoselfdefence switch and user function. Default should be off. @@ -1809,8 +1865,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Debug info. self:_DebugMessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) - - + -- This is a group that is not in the db yet. Add it n times. local assets=self:_RegisterAsset(group, n, forceattribute) @@ -1862,6 +1917,7 @@ end -- @param #string forceattribute Forced generalized attribute. -- @return #table A table containing all registered assets. function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) + self:F({groupname=group:GetName(), ngroups=ngroups, forceattribute=forceattribute}) -- Set default. local n=ngroups or 1 @@ -1877,18 +1933,15 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) return 0,0,0,0 end + -- Get name of template group. local templategroupname=group:GetName() - - local DCSgroup=group:GetDCSObject() - - local DCSunit=DCSgroup:getUnit(1) - local DCSdesc=DCSunit:getDesc() - local DCSdisplay=DCSdesc.displayName - local DCScategory=DCSgroup:getCategory() - local DCStype=DCSunit:getTypeName() + + local Descriptors=group:GetUnit(1):GetDesc() + local Category=group:GetCategory() + local TypeName=group:GetTypeName() local SpeedMax=group:GetSpeedMax() local RangeMin=group:GetRange() - local smax,sx,sy,sz=_GetObjectSize(DCSdesc) + local smax,sx,sy,sz=_GetObjectSize(Descriptors) -- Get weight and cargo bay size in kg. local weight=0 @@ -1915,7 +1968,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) end -- Set/get the generalized attribute. - local attribute=forceattribute or self:_GetAttribute(templategroupname) + local attribute=forceattribute or self:_GetAttribute(group) -- Table for returned assets. local assets={} @@ -1931,14 +1984,14 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) asset.uid=WAREHOUSE.db.AssetID asset.templatename=templategroupname asset.template=UTILS.DeepCopy(_DATABASE.Templates.Groups[templategroupname].Template) - asset.category=DCScategory - asset.unittype=DCStype + asset.category=Category + asset.unittype=TypeName asset.nunits=#asset.template.units asset.range=RangeMin asset.speedmax=SpeedMax asset.size=smax asset.weight=weight - asset.DCSdesc=DCSdesc + asset.DCSdesc=Descriptors asset.attribute=attribute asset.transporter=false -- not used yet asset.cargobay=cargobay @@ -2424,6 +2477,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Pending request. Add cargo groups to request. local Pending=Request --#WAREHOUSE.Pendingitem + -- Set time stamp. + Pending.timestamp=timer.getAbsTime() + env.info("Timestamp="..Pending.timestamp) + -- Spawn assets of this request. local _spawngroups=self:_SpawnAssetRequest(Pending) --Core.Set#SET_GROUP @@ -2963,7 +3020,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) local ncargo=request.cargogroupset:Count() -- Debug message. - local text=string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.",request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) + local text=string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.", request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) self:_DebugMessage(text, 5) -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. @@ -4117,7 +4174,7 @@ end --- Checks if the request can be fulfilled right now. -- Check for current parking situation, number of assets and transports currently in stock. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Pendingitem request The request to be checked. +-- @param #WAREHOUSE.Queueitem request The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. function WAREHOUSE:_CheckRequestNow(request) @@ -4714,8 +4771,7 @@ function WAREHOUSE:_GetIDsFromGroup(group) else self:E("WARNING: Group not found in GetIDsFromGroup() function!") end - - + end --- Filter stock assets by table entry. @@ -4781,15 +4837,13 @@ end --- Check if a group has a generalized attribute. -- @param #WAREHOUSE self --- @param #string groupname Name of the group. +-- @param Wrapper.Group#GROUP group MOOSE group object. -- @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) +function WAREHOUSE:_HasAttribute(group, attribute) if group then - local groupattribute=self:_GetAttribute(groupname) + local groupattribute=self:_GetAttribute(group) return groupattribute==attribute end @@ -4799,13 +4853,12 @@ end --- Get the generalized attribute of a group. -- Note that for a heterogenious group, the attribute is determined from the attribute of the first unit! -- @param #WAREHOUSE self --- @param #string groupname Name of the group. +-- @param Wrapper.Group#GROUP group MOOSE group object. -- @return #WAREHOUSE.Attribute Generalized attribute of the group. -function WAREHOUSE:_GetAttribute(groupname) +function WAREHOUSE:_GetAttribute(group) - local group=GROUP:FindByName(groupname) - - local attribute=WAREHOUSE.Attribute.UNKNOWN --#WAREHOUSE.Attribute + -- Default + local attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN --#WAREHOUSE.Attribute if group then @@ -4880,7 +4933,7 @@ function WAREHOUSE:_GetAttribute(groupname) elseif unarmedship then attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP else - attribute=WAREHOUSE.Attribute.UNKNOWN + attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN end end @@ -4972,21 +5025,65 @@ end -- @param #table queue Queue to print. -- @param #string name Name of the queue for info reasons. function WAREHOUSE:_PrintQueue(queue, name) - local text=string.format("%s at %s: ",name, self.alias) - for _,_qitem in ipairs(queue) do - local qitem=_qitem --#WAREHOUSE.Queueitem + + local total="Empty" + if #queue>0 then + total=string.format("Total = %d", #queue) + end + + -- Init string. + local text=string.format("%s at %s: %s",name, self.alias, total) + + for i,qitem in ipairs(queue) do + local qitem=qitem --#WAREHOUSE.Pendingitem + -- Set airbase: local airbasename="none" if qitem.airbase then airbasename=qitem.airbase:GetName() - end - text=text..string.format("\nUID=%d, Prio=%d, Requestor=%s, Airbase=%s (category=%d), Descriptor: %s=%s, #Assets=%s, Transport=%s, #Transport=%d.", - qitem.uid, qitem.prio, qitem.warehouse.alias, airbasename, qitem.category, qitem.assetdesc,tostring(qitem.assetdescval), tostring(qitem.nasset), qitem.transporttype, qitem.ntransport) + end + + local uid=qitem.uid + local prio=qitem.prio + local clock="N/A" + if qitem.timestamp then + clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) + end + local requestor=qitem.warehouse.alias + local requestorAirbaseCat=qitem.category + local assetdesc=qitem.assetdesc + local assetdescval=qitem.assetdescval + local nasset=tostring(qitem.nasset) + local ndelivered=tostring(qitem.ndelivered) + local ncargogroupset="N/A" + if qitem.cargogroupset then + ncargogroupset=tostring(qitem.cargogroupset:Count()) + end + local transporttype="N/A" + if qitem.transporttype then + transporttype=qitem.transporttype + end + local ntransport="N/A" + if qitem.ntransport then + ntransport=tostring(qitem.ntransport) + end + local ntransportalive="N/A" + if qitem.transportgroupset then + ntransportalive=tostring(qitem.transportgroupset:Count()) + end + local ntransporthome="N/A" + if qitem.ntransporthome then + ntransporthome=tostring(qitem.ntransporthome) + end + + -- Output text: + text=text..string.format( + "\n%d) UID=%d, Prio=%d, Clock=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%d / #alive=%s / #home=%s", + i, uid, prio, clock, requestor, airbasename, requestorAirbaseCat, assetdesc, assetdescval, nasset, ncargogroupset, ndelivered, transporttype, ntransport, ntransportalive, ntransporthome) + end - if #queue==0 then - text=text.."Empty." - end - self:E(self.wid..text) + + self:I(self.wid..text) end --- Display status of warehouse. From 7ab11d8fef25f2ec9afdb77f76cfb7d24dc785d6 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 5 Sep 2018 16:41:59 +0200 Subject: [PATCH 46/73] Warehouse v0.3.5w --- .../Moose/Functional/Warehouse.lua | 269 +++++++++++++----- 1 file changed, 190 insertions(+), 79 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 9d328706b..c70314a4b 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -107,15 +107,20 @@ -- The positioning of the warehouse static object is very important for a couple of reasons. Firstly, a warehouse needs a good infrastructure so that spawned assets -- have a proper road connection or can reach the associated airbase easily. -- +-- ## Constructor and Start +-- -- Once the static warehouse object is placed in the mission editor it can be used as a MOOSE warehouse by the @{#WAREHOUSE.New}(*warehousestatic*, *alias*) constructor, -- like for example: -- --- warehouse=WAREHOUSE:New(STATIC:FindByName("Warehouse Static Batumi"), "My Warehouse Alias") --- warehouse:Start() +-- warehouse=WAREHOUSE:New(STATIC:FindByName("Warehouse Static Batumi"), "My Warehouse Alias") +-- warehouse:Start() -- --- So the first parameter *warehousestatic* is the static MOOSE object. By default, the name of the warehouse will be the same as the name given to the static object. +-- The first parameter *warehousestatic* is the static MOOSE object. By default, the name of the warehouse will be the same as the name given to the static object. -- The second parameter *alias* can be used to choose a more convenient name if desired. This will be the name the warehouse calls itself when reporting messages. -- +-- Note that a warehouse also needs to be started in order to be in service. This is done with the @{#WAREHOUSE.Start}() or @{#WAREHOUSE.__Start}(*delay*) functions. +-- The warehouse is now fully operational and requests are being processed. +-- -- # Adding Assets -- -- 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}. @@ -123,8 +128,8 @@ -- -- 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) +-- infrantry=GROUP:FindByName("Some Infantry Group") +-- warehouse:AddAsset(infantry, 5) -- -- This will add five infantry groups to the warehouse stock. -- @@ -137,10 +142,10 @@ -- -- # Requesting Assets -- --- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinize to check if can be fulfilled at all. If the request is valid, it is +-- Assets of the warehouse can be requested by other MOOSE warehouses. A request will first be scrutinized to check if can be fulfilled at all. If the request is valid, it is -- put into the warehouse queue and processed as soon as possible. -- --- A request can be assed by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function. +-- A request can be added by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function. -- The parameters are -- -- * *warehouse*: The requesting MOOSE @{#WAREHOUSE}. Assets will be delivered there. @@ -152,22 +157,22 @@ -- * *Prio*: (Optional) A number between 1 (high) and 100 (low) describing the priority of the request. Request with high priority are processed first. Default is 50, i.e. medium priority. -- * *Assignment*: (Optional) A free to choose string describing the assignment. For self requests, this can be used to assign the spawned groups to specific tasks. -- --- So for example: +-- For example: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2, 20) +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2, 20) -- -- Here, warehouse Kobuleti requests 5 infantry groups from warehouse Batumi. These "cargo" assets should be transported from Batumi to Kobuleti by 2 APCS. -- Note that the warehouse at Batumi needs to have at least five infantry groups and two APC groups in their stock if the request can be processed. -- If either to few infantry or APC groups are available when the request is made, the request is held in the warehouse queue until enough cargo and -- transport assets are available. -- --- Also not that the above request is for five infantry units. So any group in stock that has the generalized attribute "INFANTRY" can be selected. +-- Also note that the above request is for five infantry groups. So any group in stock that has the generalized attribute "INFANTRY" can be selected. -- -- ## Requesting a Specific Unit Type -- -- A more specific request could look like: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.UNITTYPE, "A-10C", 2) +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.UNITTYPE, "A-10C", 2) -- -- Here, Kobuleti requests a specific unit type, in particular two groups of A-10Cs. Note that the spelling is important as it must exacly be the same as -- what one get's when using the DCS unit type. @@ -176,7 +181,7 @@ -- -- An even more specific request would be: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Group Name as in ME", 3) +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Group Name as in ME", 3) -- -- In this case three groups named "Group Name as in ME" are requested. So this explicitly request the groups named like that in the Mission Editor. -- @@ -184,7 +189,7 @@ -- -- On the other hand, very general unspecifc requests can be made as -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.Ground, 10) +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.Ground, 10) -- -- Here, Kubuleti requests 10 ground groups and does not care which ones. This could be a mix of infantry, APCs, trucks etc. -- @@ -193,7 +198,7 @@ -- Assets in the warehouse' stock can used for user defined tasks realtively easily. They can be spawned into the game by a "self request", i.e. the warehouse -- requests the assets from itself: -- --- warehouseBatumi:AddRequest(warehouseBatumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5) +-- warehouseBatumi:AddRequest(warehouseBatumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5) -- -- This would simply spawn five infantry groups in the spawn zone of the Batumi warehouse if/when they are available. -- @@ -202,21 +207,21 @@ -- If a warehouse requests assets from itself, it triggers the event **SelfReqeuest**. The mission designer can capture this event with the associated -- @{#WAREHOUSE.OnAfterSelfRequest}(*From*, *Event*, *To*, *groupset*, *request*) function. -- --- --- OnAfterSelfRequest user function. Access groups spawned from the warehouse for further tasking. --- -- @param #WAREHOUSE self --- -- @param #string From From state. --- -- @param #string Event Event. --- -- @param #string To To state. --- -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. --- -- @param #WAREHOUSE.Pendingitem request Pending self request. --- function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) --- --- for _,group in pairs(groupset:GetSetObjects()) do --- local group=group --Wrapper.Group#GROUP --- group:SmokeGreen() --- end --- --- end +-- --- OnAfterSelfRequest user function. Access groups spawned from the warehouse for further tasking. +-- -- @param #WAREHOUSE self +-- -- @param #string From From state. +-- -- @param #string Event Event. +-- -- @param #string To To state. +-- -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. +-- -- @param #WAREHOUSE.Pendingitem request Pending self request. +-- function WAREHOUSE:OnAfterSelfRequest(From, Event, To, groupset, request) +-- +-- for _,group in pairs(groupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP +-- group:SmokeGreen() +-- end +-- +-- 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 -- for further tasking. Here, the groups are only smoked but, of course, you can use them for whatever task you fancy. @@ -297,9 +302,19 @@ -- -- ## Invalid Requests -- --- Invalid request are requests which cannot be processes **ever** because there is some logical or physical argument for it. Or simply because that feature was not implemented (yet). +-- Invalid request are requests which can **never** be processes because there is some logical or physical argument against it. Or simply because that feature was not implemented (yet). -- --- * One warehuse requests airborne assets from another warehouse but at one (or even both) warehouses do not have an associated airbase. +-- * All airborne assets need an associated airbase of any kind on the sending *and* receiving warhouse. +-- * Airplanes need an airdrome at the sending and receiving warehouses. +-- * Not enough parking spots of the right terminal type at the sending warehouse. +-- * No parking spots of the right terminal type at the receiving warehouse. +-- * Ground assets need a road connection between both warehouses. +-- * Ground assets cannot be send directly to ships, i.e. warehouses on ships. +-- * Naval units need a user defined shipping lane between both warehouses. +-- * Warehouses need a user defined port zone to spawn naval assets. +-- * If transport by airplane, both warehouses must have and airdrome. +-- * If transport by APC, both warehouses must have a road connection. +-- * If transport by helicopter, the sending airbase must have an associated airbase. -- -- All invalid requests are removed from the warehouse queue! -- @@ -308,9 +323,18 @@ -- Temporarily unprocessable requests are possible in priciple, but cannot be processed at the given time the warehouse checks its queue. -- -- * No enough parking spaces are available for the requests assets but the airbase has enough parking spots in total so that this request is possible once other aircraft have taken off. +-- * Requesting warehouse is not in state "Running" (could be stopped, not yet started or under attack). +-- * Not enough cargo assets available at this moment. +-- * Not enough free parking spots for all cargo or transport airborne assets at the moment. +-- * Not enough transport assets to carry all cargo assets. -- -- Temporarily unprocessable requests are held in the queue. If at some point in time, the situation changes so that these requests can be processed, they are executed. -- +-- ## Processing Speed +-- +-- 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. +-- -- === -- -- # Strategic Considerations @@ -320,11 +344,11 @@ -- -- ## Capturing a Warehouse' Airbase -- --- If a warehouse has an associated airbase, it can be captured by the enemy. In this case, the warehouse looses it ability so employ all airborne assets and is also cut-off --- from supply by airborne units. +-- If a warehouse has an associated airbase, it can be captured by the enemy. In this case, the warehouse looses its ability so employ all airborne assets and is also cut-off +-- from supply by airplanes. Supply of ground troops via helicopters is still possible, because they deliver the troops into the spawn zone. -- --- Technically, the capturing of the airbase is triggered by the DCS S_EVENT_CAPTURE_BASE event. So the capturing takes place when only enemy ground units are in the --- airbase zone whilst no ground units of the present airbase owner are in that zone. +-- Technically, the capturing of the airbase is triggered by the DCS [S\_EVENT\_BASE\_CAPTURED](https://wiki.hoggitworld.com/view/DCS_event_base_captured) event. +-- So the capturing takes place when only enemy ground units are in the airbase zone whilst no ground units of the present airbase owner are in that zone. -- -- The warehouse will also create an event named "AirbaseCaptured", which can be captured by the @{#WAREHOUSE.OnAfterAirbaseCaptured} function. So the warehouse can react on -- this attack and for example spawn ground groups to re-capture its airbase. @@ -335,16 +359,24 @@ -- ## Capturing the Warehouse -- -- A warehouse can also be captured by the enemy coalition. If enemy ground troops enter the warehouse zone the event **Attacked** is triggered which can be captured by the --- @{#WAREHOUSE.OnAfterAttacked} event. +-- @{#WAREHOUSE.OnAfterAttacked} event. By default the warehouse zone circular zone with a radius of 500 meters located at the center of the physical warehouse. +-- The warehouse zone can be set via the @{#WAREHOUSE.SetWarehouseZone}(*zone*) function. The parameter *zone* must also be a cirular zone. -- --- If a warehouse is attacked it will spawn all its ground assets in the spawn zone which can than be used to defend the warehouse zone. +-- The @{#WAREHOUSE.OnAfterAttacked} function can be used by the mission designer to react to the enemy attack. For example by deploying some or all ground troops +-- currently in stock to defend the warehouse. Note that the warehouse also has a self defence option which can be enabled by the @{#WAREHOUSE.SetAutoDefenceOn}() +-- function. In this case, the warehouse will automatically spawn all ground troops. If the spawn zone is further away from the warehouse zone, all mobile troops +-- are routed to the warehouse zone. -- -- If only ground troops of the enemy coalition are present in the warehouse zone, the warehouse and all its assets falls into the hands of the enemy. -- In this case the event **Captured** is triggered which can be captured by the @{#WAREHOUSE.OnAfterCaptured} function. -- --- The warehouse turn to the capturing coalition, i.e. its physical representation, and all assets as well. In paticular, all requests to the warehouse will +-- The warehouse turns to the capturing coalition, i.e. its physical representation, and all assets as well. In paticular, all requests to the warehouse will -- spawn assets beloning to the new owner. -- +-- If the enemy troops could be defeated, i.e. no more troops of the opposite coalition are in the warehouse zone, the event **Defeated** is triggered and +-- the @{#WAREHOUSE.OnAfterDefeated} function can be used to adapt to the new situation. For example putting back all spawned defender troops back into +-- the warehouse stock. Note that if the automatic defence is enabled, all defenders are automatically put back into the warehouse on the **Defeated** event. +-- -- ## Destroying a Warehouse -- -- If an enemy destroy the physical warehouse structure, the warehouse will of course stop all its services. In priciple, all assets contained in the warehouse are @@ -631,7 +663,7 @@ -- After 10 seconds we make a self request for a rescue helicopter. Note, that the @{#WAREHOUSE.AddRequest} function has a parameter which lets you -- specify an "Assignment". This can be later used to identify the request and take the right actions. -- --- Once the request is processed, the @{#WAREHOUSE.OnafterSelfRequest} function is called. This is where we hook in and postprocess the spawned assets. +-- Once the request is processed, the @{#WAREHOUSE.OnAfterSelfRequest} function is called. This is where we hook in and postprocess the spawned assets. -- In particular, we use the @{AI.AI_Formation#AI_FORMATION} class to make some nice escorts for our carrier. -- -- When the resue helo is spawned, we can check that this is the correct asset and make the helo go into formation with the carrier. @@ -895,20 +927,21 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.5" +WAREHOUSE.version="0.3.5w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Test mortars! Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- TODO: Added habours as interface for transport to from warehouses? -- TODO: Set ROE for spawned groups. --- TODO: Add possibility to add active groups. Need to create a pseudo template before destroy. +-- DONE: Add possibility to add active groups. Need to create a pseudo template before destroy. <== Does not seem to be necessary any more. -- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult! --- TODO: Add a time stamp when an asset is added to the stock and for requests. +-- DONE: Add a time stamp when an asset is added to the stock and for requests. -- DONE: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? -- DONE: Add autoselfdefence switch and user function. Default should be off. -- DONE: Warehouse re-capturing not working?! @@ -1779,6 +1812,9 @@ function WAREHOUSE:onafterStatus(From, Event, To) -- Update coordinate in case we have a "moving" warehouse (e.g. on a carrier). self.coordinate=self.warehouse:GetCoordinate() + -- Check if any pending jobs are done and can be deleted from the + self:_JobDone() + -- Print status. self:_DisplayStatus() @@ -1821,6 +1857,39 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:__Status(self.dTstatus) end + +--- Function that checks if a pending job is done and can be removed from queue +-- @param #WAREHOUSE self +function WAREHOUSE:_JobDone() + + local done={} + for _,request in pairs(self.pending) do + local request=request --#WAREHOUSE.Pendingitem + + local ncargo=0 + if request.cargogroupset then + ncargo=request.cargogroupset:Count() + end + + local ntransport=0 + if request.transportgroupset then + ntransport=request.transportgroupset:Count() + end + + --TODO: Check if any transports, e.g. APCs were not used and are still standing around in the spawn zone. + -- Also planes and helos might not be used? + + if ncargo==0 and ntransport==0 then + table.insert(done, request) + end + + end + + -- Remove pending requests if done. + for _,request in pairs(done) do + self:_DeleteQueueItem(request, self.pending) + end +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. @@ -2805,7 +2874,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get group obejet. local group=Cargo:GetObject() --Wrapper.Group#GROUP - -- Get warehouse state. local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE @@ -2832,7 +2900,10 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) warehouse:I(warehouse.wid..text) -- Add carrier back to warehouse stock. Actual unit is destroyed. - warehouse:AddAsset(Carrier) + --warehouse:AddAsset(Carrier) + + -- Call arrived event for carrier. + warehouse:__Arrived(1, Carrier) end @@ -3011,17 +3082,38 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) group:SmokeOrange() end - -- Update pending request. - local request=self:_UpdatePending(group) + -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. + local request, isCargo=self:_UpdatePending(group) if request then -- Number of cargo assets still in group set. - local ncargo=request.cargogroupset:Count() + if isCargo==true then - -- Debug message. - local text=string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.", request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) - self:_DebugMessage(text, 5) + -- Current size of cargo group set. + local ncargo=request.cargogroupset:Count() + + -- Debug message. + local text=string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.", + request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) + self:_DebugMessage(text, 5) + + -- All cargo delivered. + if ncargo==0 then + self:__Delivered(5, request) + end + + elseif isCargo==false then + + -- Current size of cargo group set. + local ntransport=request.transportgroupset:Count() + + -- Debug message. + local text=string.format("Transport %d of %s arrived at warehouse %s. Assets still to deliver %d.", + request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) + self:_DebugMessage(text, 5) + + end -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. if group:IsGround() and group:GetSpeedMax()>1 then @@ -3029,12 +3121,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) end -- Move asset from pending queue into new warehouse. - request.warehouse:__AddAsset(60, group) - - -- All cargo delivered. - if request and ncargo==0 then - self:__Delivered(5, request) - end + request.warehouse:__AddAsset(60, group) end @@ -3058,6 +3145,7 @@ end -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that has arrived at its destination. -- @return #WAREHOUSE.Pendingitem The updated request from the pending queue. +-- @return #boolean If true, group is a cargo asset. If false, group is a transport asset. If nil, group is neither cargo nor transport. function WAREHOUSE:_UpdatePending(group) -- Get request from group name. @@ -3066,27 +3154,53 @@ function WAREHOUSE:_UpdatePending(group) -- Get the IDs for this group. In particular, we use the asset ID to figure out which group was delivered. local wid,aid,rid=self:_GetIDsFromGroup(group) + local isCargo=nil + if request then - -- Loop over cargo groups. - for _,_cargogroup in pairs(request.cargogroupset:GetSetObjects()) do - local cargogroup=_cargogroup --Wrapper.Group#GROUP - - -- IDs of cargo group. - local cwid,caid,crid=self:_GetIDsFromGroup(cargogroup) - - -- Remove group from cargo group set. - if caid==aid then - request.cargogroupset:Remove(cargogroup:GetName()) - request.ndelivered=request.ndelivered+1 - break + -- If this request was already delivered. + if self.delivered[rid]==true then + + -- Loop over transport groups. + for _,_transportgroup in pairs(request.transportgroupset:GetSetObjects()) do + local transportgroup=_transportgroup --Wrapper.Group#GROUP + + -- IDs of cargo group. + local cwid,caid,crid=self:_GetIDsFromGroup(transportgroup) + + -- Remove group from transport group set and increase home counter. + if caid==aid then + request.transportgroupset:Remove(transportgroup:GetName()) + request.ntransporthome=request.ntransporthome+1 + isCargo=false + break + end end + + else + + -- Loop over cargo groups. + for _,_cargogroup in pairs(request.cargogroupset:GetSetObjects()) do + local cargogroup=_cargogroup --Wrapper.Group#GROUP + + -- IDs of cargo group. + local cwid,caid,crid=self:_GetIDsFromGroup(cargogroup) + + -- Remove group from cargo group set and increase delivered counter. + if caid==aid then + request.cargogroupset:Remove(cargogroup:GetName()) + request.ndelivered=request.ndelivered+1 + isCargo=true + break + end + end + end else self:E(self.wid..string.format("WARNING: pending request could not be updated since request did not exist in pending queue!")) end - return request,wid,aid,rid + return request, isCargo end @@ -3105,11 +3219,8 @@ function WAREHOUSE:onafterDelivered(From, Event, To, request) -- Make some noise :) self:_Fireworks(request.warehouse.coordinate) - -- Add table + -- Set delivered status for this request uid. self.delivered[request.uid]=true - - -- Remove pending request: - self:_DeleteQueueItem(request, self.pending) end @@ -3923,19 +4034,19 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Check if at least one asset was requested. if request.nasset==0 then - self:E(self.wid..string.format("ERROR: Incorrect request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock.")) + self:E(self.wid..string.format("ERROR: INVALID request. Request for zero assets not possible. Can happen when, e.g. \"all\" ground assets are requests but none in stock.")) valid=false end -- Request from enemy coalition? if self.coalition~=request.warehouse.coalition then - self:E(self.wid..string.format("ERROR: Incorrect request. Requesting warehouse is of wrong coaltion! Own coalition %d. Requesting warehouse %d", self.coalition, request.warehouse.coalition)) + self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coaltion! Own coalition %d. Requesting warehouse %d", self.coalition, request.warehouse.coalition)) valid=false end -- Is receiving warehouse stopped? if request.warehouse:IsStopped() then - self:E(self.wid..string.format("ERROR: Incorrect request. Requesting warehouse is stopped!")) + self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!")) valid=false end @@ -3950,7 +4061,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Delete invalid requests. for _,_request in pairs(invalid) do - self:E(self.wid..string.format("Deleting invalid request id=%d.",_request.uid)) + self:E(self.wid..string.format("Deleting INVALID request id=%d.",_request.uid)) self:_DeleteQueueItem(_request, self.queue) end @@ -4037,7 +4148,7 @@ function WAREHOUSE:_CheckRequestValid(request) local np_destination=request.airbase:GetParkingSpotsNumber(termtype) -- Debug info. - self:E(string.format("Asset attribute = %s, terminal type = %d, spots at departure = %d, destination = %d", asset.attribute, termtype, np_departure, np_destination)) + self:T(string.format("Asset attribute = %s, terminal type = %d, spots at departure = %d, destination = %d", asset.attribute, termtype, np_departure, np_destination)) -- Not enough parking at sending warehouse. --if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then @@ -4061,7 +4172,7 @@ function WAREHOUSE:_CheckRequestValid(request) -- TODO: May needs refinement if warehouse is on land and requestor is ship in harbour?! if (request.category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) then self:E("ERROR: Incorrect request. Ground asset requested but warehouse or requestor is SHIP!") - --valid=false + valid=false end if asset_train then From a039745b0f17c13092edfc3811e3e942a461cc8d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 5 Sep 2018 21:58:44 +0200 Subject: [PATCH 47/73] Warehouse v0.3.6 moved updatepending function to addasset. not a good idea, since now the pending queue of the wrong warehouse is updated! --- .../Moose/Functional/Warehouse.lua | 108 ++++++++++++------ 1 file changed, 72 insertions(+), 36 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c70314a4b..579c2c3d1 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -927,7 +927,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.5w" +WAREHOUSE.version="0.3.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1912,9 +1912,70 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu if group then + -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. + local request, isCargo=self:_UpdatePending(group) + + if request then + + -- Number of cargo assets still in group set. + if isCargo==true then + + -- Current size of cargo group set. + local ncargo=request.cargogroupset:Count() + + -- Debug message. + local text=string.format("Cargo %d of %s added to warehouse %s stock. Assets still to deliver %d.", + request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) + self:_DebugMessage(text, 5) + + -- All cargo delivered. + if ncargo==0 then + self:__Delivered(5, request) + end + + elseif isCargo==false then + + -- Current size of cargo group set. + local ntransport=request.transportgroupset:Count() + + -- Debug message. + local text=string.format("Transport %d of %s added to warehouse %s stock. Transports still missing %d.", + request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) + self:_DebugMessage(text, 5) + + end + + -- Get the asset from the global DB. + local asset=self:_FindAssetInDB(group) + + -- Note the group is only added once, i.e. the ngroups parameter is ignored here. + -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. + if asset~=nil then + self:_DebugMessage(string.format("Adding known asset uid=%d, attribute = %s to warehouse stock.", asset.uid, asset.attribute), 5) + table.insert(self.stock, asset) + else + self:_ErrorMessage(string.format("ERROR known asset could not be found in global warehouse db!"), 0) + end + + else + + -- Debug info. + self:_DebugMessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) + + -- This is a group that is not in the db yet. Add it n times. + local assets=self:_RegisterAsset(group, n, forceattribute) + + -- Add created assets to stock of this warehouse. + for _,asset in pairs(assets) do + table.insert(self.stock, asset) + end + + end + + --[[ -- Get unique ids from group name. local wid,aid,rid=self:_GetIDsFromGroup(group) - + -- Check if this is an known or a new asset group. if aid~=nil and wid~=nil then @@ -1943,6 +2004,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu table.insert(self.stock, asset) end end + ]] -- Destroy group if it is alive. -- TODO: This causes a problem, when a completely new asset is added, i.e. not from a template group. @@ -2548,7 +2610,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Set time stamp. Pending.timestamp=timer.getAbsTime() - env.info("Timestamp="..Pending.timestamp) -- Spawn assets of this request. local _spawngroups=self:_SpawnAssetRequest(Pending) --Core.Set#SET_GROUP @@ -2567,6 +2628,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add groups to pending item. Pending.cargogroupset=_spawngroups + --Pending.cargogroupset:FilterStart() ------------------------------------------------------------------------------------------------------------------------------------ -- Self request: assets are spawned at warehouse but not transported anywhere. @@ -3081,39 +3143,11 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) if self.Debug then group:SmokeOrange() end - - -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. - local request, isCargo=self:_UpdatePending(group) + -- Get request from group name. + local request=self:_GetRequestOfGroup(group, self.pending) + if request then - - -- Number of cargo assets still in group set. - if isCargo==true then - - -- Current size of cargo group set. - local ncargo=request.cargogroupset:Count() - - -- Debug message. - local text=string.format("Cargo %d of %s arrived at warehouse %s. Assets still to deliver %d.", - request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) - self:_DebugMessage(text, 5) - - -- All cargo delivered. - if ncargo==0 then - self:__Delivered(5, request) - end - - elseif isCargo==false then - - -- Current size of cargo group set. - local ntransport=request.transportgroupset:Count() - - -- Debug message. - local text=string.format("Transport %d of %s arrived at warehouse %s. Assets still to deliver %d.", - request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) - self:_DebugMessage(text, 5) - - end -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. if group:IsGround() and group:GetSpeedMax()>1 then @@ -3121,7 +3155,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) end -- Move asset from pending queue into new warehouse. - request.warehouse:__AddAsset(60, group) + request.warehouse:__AddAsset(60, group) end @@ -3172,6 +3206,7 @@ function WAREHOUSE:_UpdatePending(group) if caid==aid then request.transportgroupset:Remove(transportgroup:GetName()) request.ntransporthome=request.ntransporthome+1 + env.info("FF transport back home # "..request.ntransporthome) isCargo=false break end @@ -3190,6 +3225,7 @@ function WAREHOUSE:_UpdatePending(group) if caid==aid then request.cargogroupset:Remove(cargogroup:GetName()) request.ndelivered=request.ndelivered+1 + env.info("FF delivered cargo # "..request.ndelivered) isCargo=true break end @@ -5113,7 +5149,7 @@ function WAREHOUSE:_DeleteQueueItem(qitem, queue) for i=1,#queue do local _item=queue[i] --#WAREHOUSE.Queueitem if _item.uid==qitem.uid then - self:E(self.wid..string.format("Deleting queue item %d.", qitem.uid)) + self:I(self.wid..string.format("Deleting queue item %d.", qitem.uid)) table.remove(queue,i) break end From 012122e8daa66d9495df7644e990b5447164a6c7 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 6 Sep 2018 16:22:56 +0200 Subject: [PATCH 48/73] Warehouse v0.3.6w --- .../Moose/Functional/Warehouse.lua | 293 ++++++++++-------- 1 file changed, 168 insertions(+), 125 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 579c2c3d1..c1e1813f8 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -45,7 +45,8 @@ -- @field #table stock Table holding all assets in stock. Table entries are of type @{#WAREHOUSE.Assetitem}. -- @field #table queue Table holding all queued requests. Table entries are of type @{#WAREHOUSE.Queueitem}. -- @field #table pending Table holding all pending requests, i.e. those that are currently in progress. Table elements are of type @{#WAREHOUSE.Pendingitem}. --- @field #table delivered Table holding all delivered requests. +-- @field #table transporting Table holding assets currently transporting cargo assets. +-- @field #table delivered Table holding all delivered requests. Table elements are #boolean. If true, all cargo has been delivered. -- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @field Core.Zone#ZONE portzone Zone defining the port of a warehouse. This is where naval assets are spawned. -- @field #table shippinglanes Table holding the user defined shipping between warehouses. @@ -342,7 +343,7 @@ -- Due to the fact that a warehouse holds (or can hold) a lot of valuable assets, it makes a (potentially) juicy target for enemy attacks. -- There are several interesting situations, which can occurr. -- --- ## Capturing a Warehouse' Airbase +-- ## Capturing a Warehouses Airbase -- -- If a warehouse has an associated airbase, it can be captured by the enemy. In this case, the warehouse looses its ability so employ all airborne assets and is also cut-off -- from supply by airplanes. Supply of ground troops via helicopters is still possible, because they deliver the troops into the spawn zone. @@ -782,6 +783,7 @@ WAREHOUSE = { stock = {}, queue = {}, pending = {}, + transporting = {}, delivered = {}, defending = {}, portzone = nil, @@ -920,14 +922,16 @@ WAREHOUSE.TransportType = { -- @type WAREHOUSE.db -- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added. -- @field #table Assets Table holding registered assets, which are of type @{Functional.Warehouse#WAREHOUSE.Assetitem}. +-- @field #table Warehouses Table holding all defined @{#WAREHOUSE} objects by their unique ids. WAREHOUSE.db = { - AssetID = 0, - Assets = {}, + AssetID = 0, + Assets = {}, + Warehouses = {} } --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.6" +WAREHOUSE.version="0.3.6w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1018,6 +1022,9 @@ function WAREHOUSE:New(warehouse, alias) self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s", self.warehouse:GetName()), warehouse:GetVec2(), 500) self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 200) + -- Add warehouse to database. + WAREHOUSE.db.Warehouses[self.uid]=self + -- Start State. self:SetStartState("NotReadyYet") @@ -1151,7 +1158,8 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE.Queueitem Request Information table of the request. - --- Triggers the FSM event "Arrived", i.e. when a group has arrived at the destination. + --- Triggers the FSM event "Arrived", i.e. when a group has arrived at the destination warehosue. + -- This function should always be called from the receiving and not the sending warehouse because assets are added back to the -- @function [parent=#WAREHOUSE] Arrived -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group Group that has arrived. @@ -1911,54 +1919,75 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu end if group then + + -- Try to get UIDs from group name. Is this group a known or a new asset? + local wid,aid,rid=self:_GetIDsFromGroup(group) - -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. - local request, isCargo=self:_UpdatePending(group) - - if request then - - -- Number of cargo assets still in group set. - if isCargo==true then + if wid and aid and rid then + --------------------------- + -- This is a known asset -- + --------------------------- - -- Current size of cargo group set. - local ncargo=request.cargogroupset:Count() + -- Get the warehouse this group belonged to. (could also be the same for self requests). + local warehouseOld=self:_FindWarehouseInDB(wid) + + -- Now get the request from the pending queue and update it. + + -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. + local request, isCargo=warehouseOld:_UpdatePending(group) + + if request then + + -- Number of cargo assets still in group set. + if isCargo==true then - -- Debug message. - local text=string.format("Cargo %d of %s added to warehouse %s stock. Assets still to deliver %d.", - request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) - self:_DebugMessage(text, 5) + -- Current size of cargo group set. + local ncargo=request.cargogroupset:Count() + + -- Debug message. + local text=string.format("Cargo %d of %s added to warehouse %s stock. Assets still to deliver %d.", + request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) + self:_DebugMessage(text, 5) + + -- All cargo delivered. + if ncargo==0 then + warehouseOld:Delivered(request) + end + + elseif isCargo==false then + + -- Current size of cargo group set. + local ntransport=request.transportgroupset:Count() + + -- Debug message. + local text=string.format("Transport %d of %s added to warehouse %s stock. Transports still missing %d.", + request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) + self:_DebugMessage(text, 5) - -- All cargo delivered. - if ncargo==0 then - self:__Delivered(5, request) end + + -- Get the asset from the global DB. + local asset=self:_FindAssetInDB(group) - elseif isCargo==false then - - -- Current size of cargo group set. - local ntransport=request.transportgroupset:Count() - - -- Debug message. - local text=string.format("Transport %d of %s added to warehouse %s stock. Transports still missing %d.", - request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) - self:_DebugMessage(text, 5) - + -- Note the group is only added once, i.e. the ngroups parameter is ignored here. + -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. + if asset~=nil then + self:_DebugMessage(string.format("Adding known asset uid=%d, attribute = %s to warehouse stock.", asset.uid, asset.attribute), 5) + table.insert(self.stock, asset) + else + self:_ErrorMessage(string.format("ERROR known asset could not be found in global warehouse db!"), 0) + end + + else + -- Request did not exist! + self:E("ERROR: Request does not exist in addAsset! This should not happen!") end - -- Get the asset from the global DB. - local asset=self:_FindAssetInDB(group) - - -- Note the group is only added once, i.e. the ngroups parameter is ignored here. - -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. - if asset~=nil then - self:_DebugMessage(string.format("Adding known asset uid=%d, attribute = %s to warehouse stock.", asset.uid, asset.attribute), 5) - table.insert(self.stock, asset) - else - self:_ErrorMessage(string.format("ERROR known asset could not be found in global warehouse db!"), 0) - end - else - + ------------------------- + -- This is a NEW asset -- + ------------------------- + -- Debug info. self:_DebugMessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) @@ -1971,47 +2000,11 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu end end - - --[[ - -- Get unique ids from group name. - local wid,aid,rid=self:_GetIDsFromGroup(group) - - -- Check if this is an known or a new asset group. - if aid~=nil and wid~=nil then - - -- We got a warehouse and asset id ==> this is an "old" group. - local asset=self:_FindAssetInDB(group) - - -- Note the group is only added once, i.e. the ngroups parameter is ignored here. - -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. - if asset~=nil then - self:_DebugMessage(string.format("Adding known asset uid=%d, attribute = %s to warehouse stock.", asset.uid, asset.attribute), 5) - table.insert(self.stock, asset) - else - self:_ErrorMessage(string.format("ERROR known asset could not be found in global warehouse db!"), 0) - end - - else - - -- Debug info. - self:_DebugMessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) - - -- This is a group that is not in the db yet. Add it n times. - local assets=self:_RegisterAsset(group, n, forceattribute) - - -- Add created assets to stock of this warehouse. - for _,asset in pairs(assets) do - table.insert(self.stock, asset) - end - end - ]] -- Destroy group if it is alive. - -- TODO: This causes a problem, when a completely new asset is added, i.e. not from a template group. - -- Need to create a "zombie" template group maybe? if group:IsAlive()==true then self:_DebugMessage(string.format("Destroying group %s.", group:GetName()), 5) - group:Destroy(true) + group:Destroy() end end @@ -2041,6 +2034,14 @@ function WAREHOUSE:_FindAssetInDB(group) return nil end +--- Find a warehouse in the global warehouse data base. +-- @param #WAREHOUSE self +-- @param #number uid The unique ID of the warehouse. +-- @return #WAREHOUSE The warehouse object or nil if no warehouse exists. +function WAREHOUSE:_FindWarehouseInDB(uid) + return WAREHOUSE.db.Warehouses[uid] +end + --- Register new asset in globase warehouse data base. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that will be added to the warehouse stock. @@ -2165,28 +2166,21 @@ function WAREHOUSE:_AssetItemInfo(asset) self:T3({Template=asset.template}) end ---- On after "AddAsset" event. Add a group to the warehouse stock. If the group is alive, it is destroyed. --- @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. -function WAREHOUSE:_AddAssetFromZombie(group, ngroups) - --TODO -end - --- Spawn a ground or naval asset in the corresponding spawn zone of the warehouse. -- @param #WAREHOUSE self +-- @param #string alias Alias name of the asset group. -- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param Core.Zone#ZONE spawnzone Zone where the assets should be spawned. -- @param boolean aioff If true, AI of ground units are set to off. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetGroundNaval(asset, request, spawnzone, aioff) +function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aioff) if asset and (asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP) then -- Prepare spawn template. - local template=self:_SpawnAssetPrepareTemplate(asset, request) + local template=self:_SpawnAssetPrepareTemplate(asset, alias) -- Initial spawn point. template.route.points[1]={} @@ -2241,18 +2235,19 @@ end --- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse. -- @param #WAREHOUSE self +-- @param #string alias Alias name of the asset group. -- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. -- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. -- @param #table parking Parking data for this asset. -- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. -- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off. -- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(asset, request, parking, uncontrolled, hotstart) +function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, hotstart) if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then -- Prepare the spawn template. - local template=self:_SpawnAssetPrepareTemplate(asset, request) + local template=self:_SpawnAssetPrepareTemplate(asset, alias) -- Set route points. if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -2364,15 +2359,15 @@ end --- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. --- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @param #string alias Alias name of the group. -- @return #table Prepared new spawn template. -function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, request) +function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) -- Create an own copy of the template! local template=UTILS.DeepCopy(asset.template) -- Set unique name. - template.name=self:_Alias(asset, request) + template.name=alias -- Set current(!) coalition and country. template.CoalitionID=self.coalition @@ -2628,7 +2623,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add groups to pending item. Pending.cargogroupset=_spawngroups - --Pending.cargogroupset:FilterStart() ------------------------------------------------------------------------------------------------------------------------------------ -- Self request: assets are spawned at warehouse but not transported anywhere. @@ -2730,7 +2724,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end -- Empty cargo group set. - CargoGroups = SET_CARGO:New():FilterDeads() + CargoGroups = SET_CARGO:New() -- Add cargo groups to set. for _i,_group in pairs(_spawngroups:GetSetObjects()) do @@ -2784,12 +2778,12 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - - -- Spawn with ALIAS here or DCS crashes! - local _alias=self:_Alias(_assetitem, Request) + + -- Create an alias name with the UIDs for the sending warehouse, asset and request. + local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) - -- Spawn plane at airport in uncontrolled state. - local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid], true) + -- Spawn plane at airport in uncontrolled state. Will get activated when cargo is loaded. + local spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem, Pending, Parking[_assetitem.uid], true) if spawngroup then -- Set state of warehouse so we can retrieve it later. @@ -2823,13 +2817,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - -- Spawn with ALIAS here or DCS crashes! - local _alias=self:_Alias(_assetitem, Request) + -- Create an alias name with the UIDs for the sending warehouse, asset and request. + local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) -- Spawn plane at airport in controlled state. They need to fly to the spawn zone. - local spawngroup=self:_SpawnAssetAircraft(_assetitem, Pending, Parking[_assetitem.uid], false) + local spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem, Pending, Parking[_assetitem.uid], false) if spawngroup then + -- Set state of warehouse so we can retrieve it later. spawngroup:SetState(spawngroup, "WAREHOUSE", self) @@ -2873,13 +2868,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get stock item. local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - -- Spawn with ALIAS here or DCS crashes! - local _alias=self:_Alias(_assetitem, Request) + -- Create an alias name with the UIDs for the sending warehouse, asset and request. + local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) -- Spawn ground asset. - local spawngroup=self:_SpawnAssetGroundNaval(_assetitem, Request, self.spawnzone) + local spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.spawnzone) if spawngroup then + -- Set state of warehouse so we can retrieve it later. spawngroup:SetState(spawngroup, "WAREHOUSE", self) @@ -2941,9 +2937,15 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Load the cargo in the warehouse. --Cargo:Load(warehouse.warehouse) + + -- Get warehouse ID of the + local wid=warehouse:_GetIDsFromGroup(group) + + -- Get the receiving warehouse. + local warehouseReceiving=warehouse:_FindWarehouseInDB(wid) - -- Trigger Arrived event. - warehouse:__Arrived(1, group) + -- Trigger Arrived event at the receiving warehouse. + warehouseReceiving:__Arrived(1, group) end --- On after BackHome event. @@ -3013,7 +3015,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) end -- Create an empty group set. - local _groupset=SET_GROUP:New():FilterDeads() + local _groupset=SET_GROUP:New() -- Table for all spawned assets. local _assets={} @@ -3032,17 +3034,15 @@ function WAREHOUSE:_SpawnAssetRequest(Request) if _assetitem.category==Group.Category.GROUND then -- Spawn ground troops. - _group=self:_SpawnAssetGroundNaval(_assetitem, Request, self.spawnzone) + _group=self:_SpawnAssetGroundNaval(_alias,_assetitem, Request, self.spawnzone) elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then - --TODO: spawn only so many groups as there are parking spots. Adjust request and create a new one with the reduced number! - -- Spawn air units. if Parking[_assetitem.uid] then - _group=self:_SpawnAssetAircraft(_assetitem, Request, Parking[_assetitem.uid], UnControlled) + _group=self:_SpawnAssetAircraft(_alias,_assetitem, Request, Parking[_assetitem.uid], UnControlled) else - _group=self:_SpawnAssetAircraft(_assetitem, Request, nil, UnControlled) + _group=self:_SpawnAssetAircraft(_alias,_assetitem, Request, nil, UnControlled) end elseif _assetitem.category==Group.Category.TRAIN then @@ -3130,7 +3130,7 @@ function WAREHOUSE:onafterUnloaded(From, Event, To, group) end end ---- On after "Arrived" event. Triggered when a group has arrived at its destination. +--- On after "Arrived" event. Triggered when a group has arrived at its destination warehouse. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -3139,13 +3139,38 @@ end function WAREHOUSE:onafterArrived(From, Event, To, group) -- Debug message and smoke. - self:_DebugMessage(string.format("Cargo %s arrived!", tostring(group:GetName())), 5) if self.Debug then group:SmokeOrange() end + -- Get request from group. + local request=self:_GetRequestOfGroup(group, self.pending) + + if request then + + -- Get the right warehouse to put the asset into + -- Transports go back to the warehouse which called this function while cargo goes into the receiving warehouse. + local warehouse=request.warehouse + if self:_GroupIsTransport(group,request) then + warehouse=self + end + + -- Debug message + self:_DebugMessage(string.format("Group %s arrived at warehouse %s!", tostring(group:GetName()), warehouse.alias), 5) + + -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. + if group:IsGround() and group:GetSpeedMax()>1 then + group:RouteGroundTo(warehouse.coordinate, group:GetSpeedMax()*0.3, "Off Road") + end + + -- Move asset from pending queue into new warehouse. + warehouse:__AddAsset(60, group) + + end + + --[[ -- Get request from group name. - local request=self:_GetRequestOfGroup(group, self.pending) + local request=self:_GetRequestOfGroup(group, self.pending) if request then @@ -3158,6 +3183,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) request.warehouse:__AddAsset(60, group) end + ]] end @@ -3696,9 +3722,6 @@ function WAREHOUSE:_OnEventArrived(EventData) -- Check if unit is alive and on the ground. Engine shutdown can also be triggered in other situations! if unit and unit:IsAlive()==true and unit:InAir()==false then - -- Smoke unit that arrived. - unit:SmokeBlue() - -- Get group. local group=EventData.IniGroup @@ -3793,7 +3816,7 @@ function WAREHOUSE:_OnEventLanding(EventData) self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) -- Get request of this group - local request=self:_GetRequestOfGroup(group,self.pending) + local request=self:_GetRequestOfGroup(group, self.pending) -- If request is nil, the cargo has been delivered. -- TODO: I might need to add a delivered table, to be better able to get this right. @@ -4839,6 +4862,26 @@ function WAREHOUSE:_GetRequestOfGroup(group, queue) end +--- Is the group a used as transporter for a given request? +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which the info is gathered. +-- @param #WAREHOUSE.Pendingitem request Request +-- @return #WAREHOUSE.Pendingitem The request belonging to this group. +function WAREHOUSE:_GroupIsTransport(group, request) + + local transporters=request.transportgroupset:GetSetObjects() + + local groupname=group:GetName() + for _,transport in pairs(transporters) do + if transport:GetName()==groupname then + return true + end + end + + return false +end + + --- Creates a unique name for spawned assets. From the group name the original warehouse, global asset and the request can be derived. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Assetitem _assetitem Asset for which the name is created. From 3f875ce27697c7f54bbee508f5edbc62db26b40f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 6 Sep 2018 23:20:47 +0200 Subject: [PATCH 49/73] Warehouse v0.3.7 Added global warehouse table. Fixed bugs for prending queue --- .../Moose/Functional/Warehouse.lua | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c1e1813f8..827033e28 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -931,7 +931,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.6w" +WAREHOUSE.version="0.3.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -3058,7 +3058,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) elseif _assetitem.category==Group.Category.SHIP then -- Spawn naval assets. - _group=self:_SpawnAssetGroundNaval(_assetitem, Request, self.portzone) + _group=self:_SpawnAssetGroundNaval(_alias,_assetitem, Request, self.portzone) else self:E(self.wid.."ERROR: Unknown asset category!") @@ -4868,16 +4868,19 @@ end -- @param #WAREHOUSE.Pendingitem request Request -- @return #WAREHOUSE.Pendingitem The request belonging to this group. function WAREHOUSE:_GroupIsTransport(group, request) + + if request.transportgroupset then - local transporters=request.transportgroupset:GetSetObjects() + local transporters=request.transportgroupset:GetSetObjects() - local groupname=group:GetName() - for _,transport in pairs(transporters) do - if transport:GetName()==groupname then - return true + local groupname=group:GetName() + for _,transport in pairs(transporters) do + if transport:GetName()==groupname then + return true + end end end - + return false end From c2064690f16ba69cf08d122b7f3d339d96a25e65 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 6 Sep 2018 23:25:19 +0200 Subject: [PATCH 50/73] AI CARGO Added new routines by Sven. Due to conflicts just overwriting the old ones. --- Moose Development/Moose/AI/AI_Cargo.lua | 435 +++++++++++ Moose Development/Moose/AI/AI_Cargo_APC.lua | 449 +---------- .../Moose/AI/AI_Cargo_Airplane.lua | 240 ++---- .../Moose/AI/AI_Cargo_Dispatcher.lua | 729 ++++++++++++++---- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 40 +- .../Moose/AI/AI_Cargo_Dispatcher_Airplane.lua | 47 +- .../AI/AI_Cargo_Dispatcher_Helicopter.lua | 49 +- .../Moose/AI/AI_Cargo_Helicopter.lua | 432 ++--------- 8 files changed, 1273 insertions(+), 1148 deletions(-) create mode 100644 Moose Development/Moose/AI/AI_Cargo.lua diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua new file mode 100644 index 000000000..885d717de --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -0,0 +1,435 @@ +--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Cargo +-- @image Cargo.JPG + +--- @type AI_CARGO +-- @extends Core.Fsm#FSM_CONTROLLABLE + + +--- Base class for the dynamic cargo handling capability for AI groups. +-- +-- Carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. +-- The AI_CARGO module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- CARGO derived objects must be declared within the mission to make the AI_CARGO object recognize the cargo. +-- Please consult the @{Cargo.Cargo} module for more information. +-- +-- The derived classes from this module are: +-- +-- * @{AI.AI_Cargo_APC} - Cargo transportation using APCs and other vehicles between zones. +-- * @{AI.AI_Cargo_Helicopter} - Cargo transportation using helicopters between zones. +-- * @{AI.AI_Cargo_Airplane} - Cargo transportation using airplanes to and from airbases. +-- +-- @field #AI_CARGO +AI_CARGO = { + ClassName = "AI_CARGO", + Coordinate = nil, -- Core.Point#COORDINATE, + Carrier_Cargo = {}, +} + +--- Creates a new AI_CARGO object. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param Core.Set#SET_CARGO CargoSet +-- @param #number CombatRadius +-- @return #AI_CARGO +function AI_CARGO:New( Carrier, CargoSet ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( Carrier ) ) -- #AI_CARGO + + self.CargoSet = CargoSet -- Core.Set#SET_CARGO + self.CargoCarrier = Carrier -- Wrapper.Group#GROUP + + self:SetStartState( "Unloaded" ) + + self:AddTransition( "Unloaded", "Pickup", "*" ) + self:AddTransition( "Loaded", "Deploy", "*" ) + + self:AddTransition( "*", "Load", "Boarding" ) + self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" ) + self:AddTransition( "Boarding", "Loaded", "Boarding" ) + self:AddTransition( "Boarding", "PickedUp", "Loaded" ) + + self:AddTransition( "Loaded", "Unload", "Unboarding" ) + self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) + self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) + self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) + + --- Pickup Handler OnBefore for AI_CARGO + -- @function [parent=#AI_CARGO] OnBeforePickup + -- @param #AI_CARGO self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. + -- @return #boolean + + --- Pickup Handler OnAfter for AI_CARGO + -- @function [parent=#AI_CARGO] OnAfterPickup + -- @param #AI_CARGO self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. + + --- Pickup Trigger for AI_CARGO + -- @function [parent=#AI_CARGO] Pickup + -- @param #AI_CARGO self + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. + + --- Pickup Asynchronous Trigger for AI_CARGO + -- @function [parent=#AI_CARGO] __Pickup + -- @param #AI_CARGO self + -- @param #number Delay + -- @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 + -- @function [parent=#AI_CARGO] OnBeforeDeploy + -- @param #AI_CARGO self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. + -- @return #boolean + + --- Deploy Handler OnAfter for AI_CARGO + -- @function [parent=#AI_CARGO] OnAfterDeploy + -- @param #AI_CARGO self + -- @param #string From + -- @param #string Event + -- @param #string To + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. + + --- Deploy Trigger for AI_CARGO + -- @function [parent=#AI_CARGO] Deploy + -- @param #AI_CARGO self + -- @param Core.Point#COORDINATE Coordinate + -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. + + --- Deploy Asynchronous Trigger for AI_CARGO + -- @function [parent=#AI_CARGO] __Deploy + -- @param #AI_CARGO self + -- @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. + + + --- Loaded Handler OnAfter for AI_CARGO + -- @function [parent=#AI_CARGO] OnAfterLoaded + -- @param #AI_CARGO self + -- @param Wrapper.Group#GROUP Carrier + -- @param #string From + -- @param #string Event + -- @param #string To + + --- Unloaded Handler OnAfter for AI_CARGO + -- @function [parent=#AI_CARGO] OnAfterUnloaded + -- @param #AI_CARGO self + -- @param Wrapper.Group#GROUP Carrier + -- @param #string From + -- @param #string Event + -- @param #string To + + for _, CarrierUnit in pairs( Carrier:GetUnits() ) do + CarrierUnit:SetCargoBayWeightLimit() + end + + self.Transporting = false + self.Relocating = false + + return self +end + + + +function AI_CARGO:IsTransporting() + + return self.Transporting == true +end + +function AI_CARGO:IsRelocating() + + return self.Relocating == true +end + + + +--- On before Load event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO:onbeforeLoad( Carrier, From, Event, To, PickupZone ) + self:F( { Carrier, From, Event, To } ) + + local Boarding = false + + local LoadInterval = 10 + local LoadDelay = 10 + local Carrier_List = {} + local Carrier_Weight = {} + + if Carrier and Carrier:IsAlive() then + self.Carrier_Cargo = {} + for _, CarrierUnit in pairs( Carrier:GetUnits() ) do + local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT + + local CargoBayFreeWeight = CarrierUnit:GetCargoBayFreeWeight() + self:F({CargoBayFreeWeight=CargoBayFreeWeight}) + + Carrier_List[#Carrier_List+1] = CarrierUnit + Carrier_Weight[CarrierUnit] = CargoBayFreeWeight + end + + local Carrier_Count = #Carrier_List + local Carrier_Index = 1 + + for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + + self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), Carrier:GetName() } ) + + local Loaded = false + + -- Try all Carriers, but start from the one according the Carrier_Index + for Carrier_Loop = 1, #Carrier_List do + + local CarrierUnit = Carrier_List[Carrier_Index] -- Wrapper.Unit#UNIT + + -- This counters loop through the available Carriers. + Carrier_Index = Carrier_Index + 1 + if Carrier_Index > Carrier_Count then + Carrier_Index = 1 + end + + if Cargo:IsUnLoaded() then -- and not Cargo:IsDeployed() then + if Cargo:IsInLoadRadius( CarrierUnit:GetCoordinate() ) then + self:F( { "In radius", CarrierUnit:GetName() } ) + + local CargoWeight = Cargo:GetWeight() + + -- Only when there is space within the bay to load the next cargo item! + if Carrier_Weight[CarrierUnit] > CargoWeight then --and CargoBayFreeVolume > CargoVolume then + Carrier:RouteStop() + --Cargo:Ungroup() + Cargo:__Board( LoadDelay, CarrierUnit, 25 ) + LoadDelay = LoadDelay + LoadInterval + self:__Board( LoadDelay, Cargo, CarrierUnit, PickupZone ) + + -- So now this CarrierUnit has Cargo that is being loaded. + -- This will be used further in the logic to follow and to check cargo status. + self.Carrier_Cargo[Cargo] = CarrierUnit + Boarding = true + Carrier_Weight[CarrierUnit] = Carrier_Weight[CarrierUnit] - CargoWeight + Loaded = true + + -- Ok, we loaded a cargo, now we can stop the loop. + break + end + end + end + + end + + if not Loaded then + -- If the cargo wasn't loaded in one of the carriers, then we need to stop the loading. + end + + end + end + + return Boarding + +end + +--- On after Board event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Cargo.Cargo#CARGO Cargo Cargo object. +-- @param Wrapper.Unit#UNIT CarrierUnit +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO:onafterBoard( Carrier, From, Event, To, Cargo, CarrierUnit, PickupZone ) + self:F( { Carrier, From, Event, To, Cargo, CarrierUnit:GetName() } ) + + if Carrier and Carrier:IsAlive() then + self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), Carrier:GetName() } ) + if not Cargo:IsLoaded() then + self:__Board( 10, Cargo, CarrierUnit, PickupZone ) + return + end + end + + self:__Loaded( 10, Cargo, CarrierUnit, PickupZone ) + +end + +--- On after Loaded event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @return #boolean Cargo loaded. +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO:onafterLoaded( Carrier, From, Event, To, Cargo, PickupZone ) + self:F( { Carrier, From, Event, To } ) + + local Loaded = true + + if Carrier and Carrier:IsAlive() then + for Cargo, CarrierUnit in pairs( self.Carrier_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), Carrier:GetName() } ) + if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then + Loaded = false + end + end + end + + if Loaded then + self:PickedUp( PickupZone ) + end + +end + +--- On after PickedUp event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO:onafterPickedUp( Carrier, From, Event, To, PickupZone ) + self:F( { Carrier, From, Event, To } ) + + Carrier:RouteResume() + +end + + + + +--- On after Unload event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +function AI_CARGO:onafterUnload( Carrier, From, Event, To, DeployZone ) + self:F( { Carrier, From, Event, To, DeployZone } ) + + local UnboardInterval = 10 + local UnboardDelay = 10 + + if Carrier and Carrier:IsAlive() then + for _, CarrierUnit in pairs( Carrier:GetUnits() ) do + local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT + Carrier:RouteStop() + for _, Cargo in pairs( CarrierUnit:GetCargo() ) do + if Cargo:IsLoaded() then + Cargo:__UnBoard( UnboardDelay ) + UnboardDelay = UnboardDelay + UnboardInterval + Cargo:SetDeployed( true ) + self:__Unboard( UnboardDelay, Cargo, CarrierUnit, DeployZone ) + end + end + end + end + +end + +--- On after Unboard event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string Cargo.Cargo#CARGO Cargo Cargo object. +-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +function AI_CARGO:onafterUnboard( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:F( { Carrier, From, Event, To, Cargo:GetName() } ) + + if Carrier and Carrier:IsAlive() then + if not Cargo:IsUnLoaded() then + self:__Unboard( 10, Cargo, CarrierUnit, DeployZone ) + return + end + end + + self:Unloaded( Cargo, CarrierUnit, DeployZone ) + +end + +--- On after Unloaded event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @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. +-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +function AI_CARGO:onafterUnloaded( Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:F( { Carrier, From, Event, To, Cargo:GetName(), DeployZone = DeployZone } ) + + local AllUnloaded = true + + --Cargo:Regroup() + + if Carrier and Carrier:IsAlive() then + for _, CarrierUnit in pairs( Carrier:GetUnits() ) do + local CarrierUnit = CarrierUnit -- Wrapper.Unit#UNIT + local IsEmpty = CarrierUnit:IsCargoEmpty() + self:I({ IsEmpty = IsEmpty }) + if not IsEmpty then + AllUnloaded = false + break + end + end + + if AllUnloaded == true then + if DeployZone == true then + self.Carrier_Cargo = {} + end + self.CargoCarrier = Carrier + end + end + + if AllUnloaded == true then + self:__Deployed( 5, DeployZone ) + end + +end + +--- On after Deployed event. +-- @param #AI_CARGO self +-- @param Wrapper.Group#GROUP Carrier +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +function AI_CARGO:onafterDeployed( Carrier, From, Event, To, DeployZone ) + self:F( { Carrier, From, Event, To, DeployZone = DeployZone } ) + + self:__Guard( 0.1 ) + +end + diff --git a/Moose Development/Moose/AI/AI_Cargo_APC.lua b/Moose Development/Moose/AI/AI_Cargo_APC.lua index 71434f10d..8b2bd9eee 100644 --- a/Moose Development/Moose/AI/AI_Cargo_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_APC.lua @@ -1,4 +1,4 @@ ---- **AI** -- (R2.3) - Models the intelligent transportation of infantry and other cargo. +--- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo. -- -- === -- @@ -10,15 +10,16 @@ -- @image AI_Cargo_Dispatching_For_APC.JPG --- @type AI_CARGO_APC --- @extends Core.Fsm#FSM_CONTROLLABLE +-- @extends AI.AI_Cargo#AI_CARGO ---- Brings a dynamic cargo handling capability for AI groups. +--- Brings a dynamic cargo handling capability for an AI vehicle group. -- -- Armoured Personnel Carriers (APC), Trucks, Jeeps and other ground based carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI\_CARGO\APC module uses the @{Cargo} capabilities within the MOOSE framework. --- CARGO derived objects must be declared within the mission to make the AI\_CARGO\APC object recognize the cargo. --- Please consult the @{Cargo} module for more information. +-- +-- The AI_CARGO_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_APC object recognize the cargo. +-- Please consult the @{Cargo.Cargo} module for more information. -- -- ## Cargo loading. -- @@ -74,7 +75,6 @@ AI_CARGO_APC = { ClassName = "AI_CARGO_APC", Coordinate = nil, -- Core.Point#COORDINATE, - APC_Cargo = {}, } --- Creates a new AI_CARGO_APC object. @@ -85,125 +85,21 @@ AI_CARGO_APC = { -- @return #AI_CARGO_APC function AI_CARGO_APC:New( APC, CargoSet, CombatRadius ) - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_APC + local self = BASE:Inherit( self, AI_CARGO:New( APC, CargoSet ) ) -- #AI_CARGO_APC - self.CargoSet = CargoSet -- Core.Set#SET_CARGO self.CombatRadius = CombatRadius - self:SetStartState( "Unloaded" ) - - self:AddTransition( "Unloaded", "Pickup", "*" ) - self:AddTransition( "Loaded", "Deploy", "*" ) - - self:AddTransition( "*", "Load", "Boarding" ) - self:AddTransition( { "Boarding", "Loaded" }, "Board", "Boarding" ) - self:AddTransition( "Boarding", "Loaded", "Loaded" ) - self:AddTransition( "Loaded", "Unload", "Unboarding" ) - self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) - self:AddTransition( { "Unboarding", "Unloaded" }, "Unloaded", "Unloaded" ) - self:AddTransition( "*", "Monitor", "*" ) self:AddTransition( "*", "Follow", "Following" ) self:AddTransition( "*", "Guard", "Unloaded" ) self:AddTransition( "*", "Home", "*" ) - self:AddTransition( "*", "BackHome" , "*" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) - - --- Pickup Handler OnBefore for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] OnBeforePickup - -- @param #AI_CARGO_APC self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - -- @return #boolean - - --- Pickup Handler OnAfter for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] OnAfterPickup - -- @param #AI_CARGO_APC self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Pickup Trigger for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] Pickup - -- @param #AI_CARGO_APC self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Pickup Asynchronous Trigger for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] __Pickup - -- @param #AI_CARGO_APC self - -- @param #number Delay - -- @param Core.Point#COORDINATE Coordinate 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 - -- @function [parent=#AI_CARGO_APC] OnBeforeDeploy - -- @param #AI_CARGO_APC self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - -- @return #boolean - - --- Deploy Handler OnAfter for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] OnAfterDeploy - -- @param #AI_CARGO_APC self - -- @param #string From - -- @param #string Event - -- @param #string To - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Trigger for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] Deploy - -- @param #AI_CARGO_APC self - -- @param Core.Point#COORDINATE Coordinate - -- @param #number Speed Speed in km/h. Default is 50% of max possible speed the group can do. - - --- Deploy Asynchronous Trigger for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] __Deploy - -- @param #AI_CARGO_APC self - -- @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. - - - --- Loaded Handler OnAfter for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] OnAfterLoaded - -- @param #AI_CARGO_APC self - -- @param Wrapper.Group#GROUP APC - -- @param #string From - -- @param #string Event - -- @param #string To - - --- Unloaded Handler OnAfter for AI_CARGO_APC - -- @function [parent=#AI_CARGO_APC] OnAfterUnloaded - -- @param #AI_CARGO_APC self - -- @param Wrapper.Group#GROUP APC - -- @param #string From - -- @param #string Event - -- @param #string To - - self:__Monitor( 1 ) self:SetCarrier( APC ) - for _, APCUnit in pairs( APC:GetUnits() ) do - APCUnit:SetCargoBayWeightLimit() - end - - self.Transporting = false - self.Relocating = false - return self end @@ -256,16 +152,6 @@ function AI_CARGO_APC:SetCarrier( CargoCarrier ) end -function AI_CARGO_APC:IsTransporting() - - return self.Transporting == true -end - -function AI_CARGO_APC:IsRelocating() - - return self.Relocating == true -end - --- Find a free Carrier within a range. -- @param #AI_CARGO_APC self -- @param Core.Point#COORDINATE Coordinate @@ -359,7 +245,7 @@ function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) if APC and APC:IsAlive() then if self.CarrierCoordinate then - if self:IsRelocating() == true then + if self:IsTransporting() == true then local Coordinate = APC:GetCoordinate() self.Zone:Scan( { Object.Category.UNIT } ) if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then @@ -370,14 +256,16 @@ function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) else if self:Is( "Loaded" ) then -- There are enemies within combat range. Unload the CargoCarrier. - self:__Unload( 1, false ) + self:__Unload( 1 ) else if self:Is( "Unloaded" ) then self:Follow() end + self:F( "I am here" .. self:GetCurrentState() ) if self:Is( "Following" ) then - for APCUnit, Cargo in pairs( self.APC_Cargo ) do + for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do local Cargo = Cargo -- Cargo.Cargo#CARGO + local APCUnit = APCUnit -- Wrapper.Unit#UNIT if Cargo:IsAlive() then if not Cargo:IsNear( APCUnit, 40 ) then APCUnit:RouteStop() @@ -406,249 +294,6 @@ function AI_CARGO_APC:onafterMonitor( APC, From, Event, To ) end ---- On before Load 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. -function AI_CARGO_APC:onbeforeLoad( APC, From, Event, To ) - self:F( { APC, From, Event, To } ) - - local Boarding = false - - if APC and APC:IsAlive() then - self.APC_Cargo = {} - for _, APCUnit in pairs( APC:GetUnits() ) do - local APCUnit = APCUnit -- Wrapper.Unit#UNIT - - local CargoBayFreeWeight = APCUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - --for _, Cargo in pairs( self.CargoSet:GetSet() ) do - for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { IsUnLoaded = Cargo:IsUnLoaded(), IsDeployed = Cargo:IsDeployed(), Cargo:GetName(), APC:GetName() } ) - if Cargo:IsUnLoaded() then -- and not Cargo:IsDeployed() then - if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then - self:F( { "In radius", APCUnit:GetName() } ) - - local CargoWeight = Cargo:GetWeight() - - -- 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, APCUnit ) - - -- 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 - end - end - - return Boarding - -end - ---- On after Board 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 Cargo.Cargo#CARGO Cargo Cargo object. --- @param Wrapper.Unit#UNIT APCUnit -function AI_CARGO_APC:onafterBoard( APC, From, Event, To, Cargo, APCUnit ) - self:F( { APC, From, Event, To, Cargo, APCUnit:GetName() } ) - - if APC and APC:IsAlive() then - self:F({ IsLoaded = Cargo:IsLoaded(), Cargo:GetName(), APC:GetName() } ) - if not Cargo:IsLoaded() then - self:__Board( 10, Cargo, APCUnit ) - else - local CargoBayFreeWeight = APCUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - for _, Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - if Cargo:IsUnLoaded() then - if Cargo:IsInLoadRadius( APCUnit:GetCoordinate() ) then - local CargoWeight = Cargo:GetWeight() - - -- 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, APCUnit ) - -- 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 - self:__Loaded( 5, Cargo ) - end - end - -end - ---- On before Loaded 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. --- @return #boolean Cargo loaded. -function AI_CARGO_APC:onbeforeLoaded( APC, From, Event, To, Cargo ) - self:F( { APC, From, Event, To } ) - - local Loaded = true - - if APC and APC:IsAlive() then - for APCUnit, Cargo in pairs( self.APC_Cargo ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { IsLoaded = Cargo:IsLoaded(), IsDestroyed = Cargo:IsDestroyed(), Cargo:GetName(), APC:GetName() } ) - if not Cargo:IsLoaded() and not Cargo:IsDestroyed() then - Loaded = false - end - end - end - - if Loaded == true then - APC:RouteResume() - end - - return Loaded - -end - - - - - ---- On after Unload 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 #boolean Deployed Cargo is deployed. -function AI_CARGO_APC:onafterUnload( APC, From, Event, To, Deployed ) - self:F( { APC, From, Event, To, Deployed } ) - - if APC and APC:IsAlive() then - for _, APCUnit in pairs( APC:GetUnits() ) do - local APCUnit = APCUnit -- Wrapper.Unit#UNIT - APC:RouteStop() - for _, Cargo in pairs( APCUnit:GetCargo() ) do - if Cargo:IsLoaded() then - Cargo:UnBoard() - Cargo:SetDeployed( true ) - self:__Unboard( 10, Cargo, Deployed ) - end - end - end - end - -end - ---- On after Unboard 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. -function AI_CARGO_APC:onafterUnboard( APC, From, Event, To, Cargo, Deployed ) - self:F( { APC, From, Event, To, Cargo:GetName() } ) - - if APC and APC:IsAlive() then - 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 - -end - ---- On before 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:onbeforeUnloaded( APC, From, Event, To, Cargo, Deployed ) - self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } ) - - local AllUnloaded = true - - --Cargo:Regroup() - - if APC and APC:IsAlive() then - for _, APCUnit in pairs( APC:GetUnits() ) do - local APCUnit = APCUnit -- Wrapper.Unit#UNIT - local IsEmpty = APCUnit:IsCargoEmpty() - self:I({ IsEmpty = IsEmpty }) - if not IsEmpty then - AllUnloaded = false - break - end - end - - if AllUnloaded == true then - if Deployed == true then - self.APC_Cargo = {} - end - self:Guard() - self.CargoCarrier = APC - end - end - - self:F( { AllUnloaded = AllUnloaded } ) - return AllUnloaded - -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 -- @param Wrapper.Group#GROUP APC @@ -660,7 +305,7 @@ function AI_CARGO_APC:onafterFollow( APC, From, Event, To ) self:F( "Follow" ) if APC and APC:IsAlive() then - for APCUnit, Cargo in pairs( self.APC_Cargo ) do + for Cargo, APCUnit in pairs( self.Carrier_Cargo ) do local Cargo = Cargo -- Cargo.Cargo#CARGO if Cargo:IsUnLoaded() then self:FollowToCarrier( self, APCUnit, Cargo ) @@ -674,25 +319,25 @@ end --- @param #AI_CARGO_APC -- @param Wrapper.Group#GROUP APC -function AI_CARGO_APC._Pickup( APC, self ) +function AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) APC:F( { "AI_CARGO_APC._Pickup:", APC:GetName() } ) if APC:IsAlive() then - self:Load() - self.Relocating = true + self:Load( PickupZone ) + self.Relocating = false + self.Transporting = true end end ---- @param #AI_CARGO_APC --- @param Wrapper.Group#GROUP APC -function AI_CARGO_APC._Deploy( APC, self ) +function AI_CARGO_APC._Deploy( APC, self, Coordinate, DeployZone ) APC:F( { "AI_CARGO_APC._Deploy:", APC } ) if APC:IsAlive() then - self:Unload( true ) + self:Unload( DeployZone ) + self.Transporting = false self.Relocating = false end end @@ -707,7 +352,8 @@ end -- @param To -- @param Core.Point#COORDINATE Coordinate of the pickup point. -- @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_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed ) +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed, PickupZone ) if APC and APC:IsAlive() then @@ -718,7 +364,7 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed ) local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true ) - local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self ) + local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Pickup", self, Coordinate, Speed, PickupZone ) self:F({Waypoints = Waypoints}) local Waypoint = Waypoints[#Waypoints] @@ -726,10 +372,11 @@ function AI_CARGO_APC:onafterPickup( APC, From, Event, To, Coordinate, Speed ) APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. else - AI_CARGO_APC._Pickup( APC, self ) + AI_CARGO_APC._Pickup( APC, self, Coordinate, Speed, PickupZone ) end - - self.Transporting = true + + self.Relocating = true + self.Transporting = false end end @@ -743,7 +390,7 @@ end -- @param To -- @param Core.Point#COORDINATE Coordinate Deploy place. -- @param #number Speed Speed in km/h to drive to the depoly coordinate. Default is 50% of max possible speed the unit can go. -function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed ) +function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed, DeployZone ) if APC and APC:IsAlive() then @@ -753,13 +400,16 @@ function AI_CARGO_APC:onafterDeploy( APC, From, Event, To, Coordinate, Speed ) local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true ) - local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self ) + local TaskFunction = APC:TaskFunction( "AI_CARGO_APC._Deploy", self, Coordinate, DeployZone ) self:F({Waypoints = Waypoints}) local Waypoint = Waypoints[#Waypoints] APC:SetTaskWaypoint( Waypoint, TaskFunction ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone. APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. + + self.Relocating = false + self.Transporting = true end end @@ -773,48 +423,21 @@ end -- @param To -- @param Core.Point#COORDINATE Coordinate Home place. -- @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_APC:onafterHome( APC, From, Event, To, Coordinate, Speed ) +function AI_CARGO_APC:onafterHome( APC, From, Event, To, Coordinate, Speed, HomeZone ) if APC and APC:IsAlive() ~= nil then self.RouteHome = true - local _speed=Speed or APC:GetSpeedMax()*0.5 + Speed = Speed or APC:GetSpeedMax()*0.5 - local Waypoints = APC:TaskGroundOnRoad( Coordinate, _speed, "Line abreast", true ) + local Waypoints = APC:TaskGroundOnRoad( Coordinate, Speed, "Line abreast", true ) self:F({Waypoints = Waypoints}) local Waypoint = Waypoints[#Waypoints] - - -- Task function triggering the arrived event. - local TaskFunction = APC:TaskFunction("AI_CARGO_APC._BackHome", self) - - -- Put task function on last waypoint. - APC:SetTaskWaypoint( Waypoint, TaskFunction ) APC:Route( Waypoints, 1 ) -- Move after a random seconds to the Route. See the Route method for details. end end - ---- Function called when transport is back home and nothing more to do. Triggering the event BackHome. --- @param Wrapper.Group#GROUP APC Cargo carrier. --- @param #AI_CARGO_APC self -function AI_CARGO_APC._BackHome(APC, self) - --Trigger BackHome event. - env.info(string.format("FF APC %s is back home task function!",APC:GetName())) - APC:SmokeGreen() - self:__BackHome(1) -end - ---- On after BackHome event. --- @param #AI_CARGO_APC self --- @param Wrapper.Group#GROUP APC --- @param From --- @param Event --- @param To -function AI_CARGO_APC:onafterBackHome( APC, From, Event, To ) - env.info(string.format("FF APC %s is back home event!",APC:GetName())) - APC:SmokeRed() -end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index be200d882..0b4be9907 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -1,4 +1,4 @@ ---- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo). +--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo). -- -- === -- @@ -13,12 +13,39 @@ -- @extends Core.Fsm#FSM_CONTROLLABLE ---- Implements the transportation of cargo by airplanes. +--- Brings a dynamic cargo handling capability for an AI airplane group. +-- +-- Airplane carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation between airbases. +-- +-- The AI_CARGO_AIRPLANE module uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- @{Cargo.Cargo} must be declared within the mission to make AI_CARGO_AIRPLANE recognize the cargo. +-- Please consult the @{Cargo.Cargo} module for more information. +-- +-- ## Cargo pickup. +-- +-- Using the @{#AI_CARGO_AIRPLANE.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate. +-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! +-- +-- ## Cargo deployment. +-- +-- Using the @{#AI_CARGO_AIRPLANE.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. +-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! +-- +-- ## Infantry health. +-- +-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. +-- As a result, the unboarding infantry is very _healthy_ every time it unboards. +-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. +-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. +-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has +-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every +-- time is not so much of an issue ... +-- -- -- @field #AI_CARGO_AIRPLANE AI_CARGO_AIRPLANE = { ClassName = "AI_CARGO_AIRPLANE", - Coordinate = nil -- Core.Point#COORDINATE, + Coordinate = nil, -- Core.Point#COORDINATE } --- Creates a new AI_CARGO_AIRPLANE object. @@ -28,21 +55,7 @@ AI_CARGO_AIRPLANE = { -- @return #AI_CARGO_AIRPLANE function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_AIRPLANE - - self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP - - self:SetStartState( "Unloaded" ) - - self:AddTransition( { "Unloaded", "Loaded" }, "Pickup", "*" ) - self:AddTransition( "Loaded", "Deploy", "*" ) - - 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" ) + local self = BASE:Inherit( self, AI_CARGO:New( Airplane, CargoSet ) ) -- #AI_CARGO_AIRPLANE self:AddTransition( "*", "Landed", "*" ) self:AddTransition( "*", "Home" , "*" ) @@ -132,7 +145,7 @@ function AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) AirplaneUnit:SetCargoBayWeightLimit() end - self.Relocating = false --FF should be false or set according to state of airplane! + self.Relocating = true return self end @@ -245,7 +258,7 @@ function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) -- Aircraft was sent to this airbase to pickup troops. Initiate loadling. if self.RoutePickup == true then env.info("FF load airplane "..Airplane:GetName()) - self:Load( Airplane:GetCoordinate() ) + self:Load( self.PickupZone ) self.RoutePickup = false self.Relocating = true end @@ -269,12 +282,15 @@ end -- @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 Core.Point#COORDINATE Coordinate -- @param #number Speed in km/h for travelling to pickup base. -function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Speed ) +-- @param Core.Zone#ZONE_AIRBASE PickupZone +function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, PickupZone ) if Airplane and Airplane:IsAlive()~=nil then env.info("FF onafterpick aircraft alive") + + self.PickupZone = PickupZone -- Get closest airbase of current position. local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() @@ -288,8 +304,10 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Sp self.Airbase=ClosestAirbase end + local Airbase = PickupZone:GetAirbase() + -- 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()) + local Dist = Airbase:GetCoordinate():Get2DDistance(ClosestAirbase:GetCoordinate()) env.info("Distance closest to pickup airbase = "..Dist) if Airplane:InAir() or Dist>500 then @@ -305,9 +323,6 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Sp -- Aircraft is on a pickup mission. self.RoutePickup = true - -- Unclear!? - self.Transporting = true - self.Relocating = false else env.info("FF onafterpick calling landed") @@ -316,9 +331,13 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Airbase, Sp self:Landed() end + + self.Transporting = false + self.Relocating = true else env.info("FF onafterpick aircraft not alive") end + end @@ -328,12 +347,15 @@ end -- @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 ) +-- @param Core.Point#COORDINATE Coordinate +-- @param #number Speed in km/h for travelling to pickup base. +-- @param Core.Zone#ZONE_AIRBASE DeployZone +function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Coordinate, Speed, DeployZone ) if Airplane and Airplane:IsAlive()~=nil then + local Airbase = DeployZone:GetAirbase() + -- Activate uncontrolled airplane. if Airplane:IsAlive()==false then Airplane:SetCommand({id = 'Start', params = {}}) @@ -355,116 +377,18 @@ function AI_CARGO_AIRPLANE:onafterDeploy( Airplane, From, Event, To, Airbase, Sp end ---- On before 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:onbeforeLoad( Airplane, From, Event, To, Coordinate ) - - local Boarding = false - - if Airplane and Airplane:IsAlive() ~= nil then - - for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do - local AirplaneUnit = AirplaneUnit -- Wrapper.Unit#UNIT - 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 - - Cargo:Board( AirplaneUnit, 25 ) - self:__Board( 5, Cargo, AirplaneUnit ) - Boarding = true - break - end - end - - end - end - end - - return Boarding - -end - ---- On after Board event. Cargo is inside the load radius and boarding is performed. +--- On after PickedUp event. All 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. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param Wrapper.Unit#UNIT AirplaneUnit -function AI_CARGO_AIRPLANE:onafterBoard( Airplane, From, Event, To, Cargo, AirplaneUnit ) +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO_AIRPLANE:onafterPickedUp( Airplane, From, Event, To, PickupZone ) + self:F( { AirplaneGroup, From, Event, To } ) if Airplane and Airplane:IsAlive() then - - self:F({ IsLoaded = Cargo:IsLoaded() } ) - - if not Cargo:IsLoaded() then - self:__Board( 10, Cargo, AirplaneUnit ) - else - local CargoBayFreeWeight = AirplaneUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - -- Check if another cargo can be loaded into the airplane. - for _,_Cargo in UTILS.spairs( self.CargoSet:GetSet(), function( t, a, b ) return t[a]:GetWeight() > t[b]:GetWeight() end ) 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 = AirplaneUnit: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 - ---- 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 ) - - 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 @@ -476,7 +400,10 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To ) +function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To, DeployZone ) + + local UnboardInterval = 10 + local UnboardDelay = 10 if Airplane and Airplane:IsAlive() then for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do @@ -489,68 +416,35 @@ function AI_CARGO_AIRPLANE:onafterUnload( Airplane, From, Event, To ) self:T( { CargoCarrierHeading, CargoDeployHeading } ) local CargoDeployCoordinate = Airplane:GetPointVec2():Translate( 150, CargoDeployHeading ) - Cargo:UnBoard( CargoDeployCoordinate ) + Cargo:__UnBoard( UnboardDelay, CargoDeployCoordinate ) + UnboardDelay = UnboardDelay + UnboardInterval Cargo:SetDeployed( true ) - self:__Unboard( 10, Cargo ) + self:__Unboard( UnboardDelay, Cargo, AirplaneUnit, DeployZone ) end end end end ---- 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 - for _, AirplaneUnit in pairs( Airplane:GetUnits() ) do - local Cargos = AirplaneUnit: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 -end - ---- On after Unloaded event. Cargo has been unloaded, i.e. the unboarding process is finished. +--- On after Deployed event. -- @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 } ) +function AI_CARGO_AIRPLANE:onafterDeployed( Airplane, From, Event, To, DeployZone ) 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. diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index 777c3cff8..eb448955f 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -16,52 +16,360 @@ --- A dynamic cargo handling capability for AI groups. -- -- Carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI\_CARGO\_DISPATCHER module uses the @{Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects --- to transport @{Cargo} towards several deploy zones. --- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER object recognize the cargo. --- Please consult the @{Cargo} module for more information. +-- The AI_CARGO_DISPATCHER module uses the @{Cargo.Cargo} capabilities within the MOOSE framework, to enable Carrier GROUP objects +-- to transport @{Cargo.Cargo} towards several deploy zones. +-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_DISPATCHER object recognize the cargo. +-- Please consult the @{Cargo.Cargo} module for more information. -- --- ## 1. AI\_CARGO\_DISPATCHER constructor +-- # 1) AI_CARGO_DISPATCHER constructor -- -- * @{#AI_CARGO_DISPATCHER.New}(): Creates a new AI\_CARGO\_DISPATCHER object. -- --- ## 2. AI\_CARGO\_DISPATCHER is a FSM +-- # 2) AI_CARGO_DISPATCHER is a FSM -- --- ![Process](..\Presentations\AI_PATROL\Dia2.JPG) +-- This section must be read as follows. Each of the rows indicate a state transition, triggered through an event, and with an ending state of the event was executed. +-- The first column is the **From** state, the second column the **Event**, and the third column the **To** state. -- --- ### 2.1. AI\_CARGO\_DISPATCHER States +-- So, each of the rows have the following structure. +-- +-- * **From** => **Event** => **To** +-- +-- Important to know is that an event can only be executed if the **current state** is the **From** state. +-- This, when an **Event** that is being triggered has a **From** state that is equal to the **Current** state of the state machine, the event will be executed, +-- and the resulting state will be the **To** state. +-- +-- These are the different possible state transitions of this state machine implementation: +-- +-- * Idle => Start => Monitoring +-- * Monitoring => Monitor => Monitoring +-- * Monitoring => Stop => Idle +-- +-- * Monitoring => Pickup => Monitoring +-- * Monitoring => Load => Monitoring +-- * Monitoring => Loading => Monitoring +-- * Monitoring => Loaded => Monitoring +-- * Monitoring => PickedUp => Monitoring +-- * Monitoring => Deploy => Monitoring +-- * Monitoring => Unload => Monitoring +-- * Monitoring => Unloaded => Monitoring +-- * Monitoring => Deployed => Monitoring +-- * Monitoring => Home => Monitoring +-- +-- +-- ## 2.1) AI_CARGO_DISPATCHER States -- -- * **Monitoring**: The process is dispatching. -- * **Idle**: The process is idle. -- --- ### 2.2. AI\_CARGO\_DISPATCHER Events +-- ## 2.2) AI_CARGO_DISPATCHER Events -- --- * **Monitor**: Monitor and take action. -- * **Start**: Start the transport process. -- * **Stop**: Stop the transport process. +-- * **Monitor**: Monitor and take action. +-- -- * **Pickup**: Pickup cargo. -- * **Load**: Load the cargo. +-- * **Loading**: The dispatcher is coordinating the loading of a cargo. -- * **Loaded**: Flag that the cargo is loaded. +-- * **PickedUp**: The dispatcher has loaded all requested cargo into the CarrierGroup. -- * **Deploy**: Deploy cargo to a location. -- * **Unload**: Unload the cargo. -- * **Unloaded**: Flag that the cargo is unloaded. +-- * **Deployed**: All cargo is unloaded from the carriers in the group. -- * **Home**: A Carrier is going home. -- --- ## 3. Set the pickup parameters. +-- # 3) Enhance your mission scripts with **Tailored** Event Handling! +-- +-- Use these methods to capture the events and tailor the events with your own code! +-- All classes derived from AI_CARGO_DISPATCHER can capture these events, and you can write your own code. +-- +-- In order to properly capture the events, it is mandatory that you execute the following actions using your script: +-- +-- * Copy / Paste the code section into your script. +-- * Change the CLASS literal to the object name you have in your script. +-- * Within the function, you can now write your own code! +-- * IntelliSense will recognize the type of the variables provided by the function. Note: the From, Event and To variables can be safely ignored, +-- but you need to declare them as they are automatically provided by the event handling system of MOOSE. +-- +-- You can send messages or fire off any other events within the code section. The sky is the limit! +-- +-- ## 3.1) Tailor the **Pickup** event +-- +-- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- Pickup event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. +-- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. +-- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. +-- function CLASS:OnAfterPickup( From, Event, To, CarrierGroup, Coordinate, Speed, PickupZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.2) Tailor the **Load** event +-- +-- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- Load event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. +-- function CLASS:OnAfterLoad( From, Event, To, CarrierGroup, PickupZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.3) Tailor the **Loading** event +-- +-- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- Loading event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Cargo.Cargo#CARGO Cargo The cargo object. +-- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. +-- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. +-- function CLASS:OnAfterLoading( From, Event, To, CarrierGroup, Cargo, CarrierUnit, PickupZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.4) Tailor the **Loaded** event +-- +-- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. +-- +-- The function provides the CarrierGroup, which is the main group that was loading the Cargo into the CarrierUnit. +-- A CarrierUnit is part of the larger CarrierGroup. +-- +-- +-- --- Loaded event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. +-- -- A CarrierUnit can be part of the larger CarrierGroup. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Cargo.Cargo#CARGO Cargo The cargo object. +-- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. +-- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. +-- function CLASS:OnAfterLoaded( From, Event, To, CarrierGroup, Cargo, CarrierUnit, PickupZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.5) Tailor the **PickedUp** event +-- +-- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- PickedUp event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. +-- function CLASS:OnAfterPickedUp( From, Event, To, CarrierGroup, PickupZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.6) Tailor the **Deploy** event +-- +-- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- Deploy event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. +-- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. +-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +-- function CLASS:OnAfterDeploy( From, Event, To, CarrierGroup, Coordinate, Speed, DeployZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.7) Tailor the **Unload** event +-- +-- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- Unload event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +-- function CLASS:OnAfterUnload( From, Event, To, CarrierGroup, DeployZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.8) Tailor the **Unloading** event +-- +-- +-- --- UnLoading event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Cargo.Cargo#CARGO Cargo The cargo object. +-- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. +-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +-- function CLASS:OnAfterUnload( From, Event, To, CarrierGroup, Cargo, CarrierUnit, DeployZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.9) Tailor the **Unloaded** event +-- +-- +-- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- --- Unloaded event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. +-- -- A CarrierUnit can be part of the larger CarrierGroup. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Cargo.Cargo#CARGO Cargo The cargo object. +-- -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. +-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +-- function CLASS:OnAfterUnloaded( From, Event, To, CarrierGroup, Cargo, CarrierUnit, DeployZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- ## 3.10) Tailor the **Deployed** event +-- +-- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- +-- --- Deployed event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +-- function CLASS:OnAfterDeployed( From, Event, To, CarrierGroup, DeployZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- ## 3.11) Tailor the **Home** event +-- +-- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. +-- You can use this event handler to post messages to players, or provide status updates etc. +-- +-- --- Home event handler OnAfter for CLASS. +-- -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. +-- -- You can use this event handler to post messages to players, or provide status updates etc. +-- -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo and this event won't be triggered. +-- -- @param #CLASS self +-- -- @param #string From A string that contains the "*from state name*" when the event was triggered. +-- -- @param #string Event A string that contains the "*event name*" when the event was triggered. +-- -- @param #string To A string that contains the "*to state name*" when the event was triggered. +-- -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. +-- -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. +-- -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. +-- -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +-- function CLASS:OnAfterHome( From, Event, To, CarrierGroup, Coordinate, Speed, HomeZone ) +-- +-- -- Write here your own code. +-- +-- end +-- +-- +-- # 3) Set the pickup parameters. -- -- Several parameters can be set to pickup cargo: -- -- * @{#AI_CARGO_DISPATCHER.SetPickupRadius}(): Sets or randomizes the pickup location for the carrier around the cargo coordinate in a radius defined an outer and optional inner radius. -- * @{#AI_CARGO_DISPATCHER.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. -- --- ## 4. Set the deploy parameters. +-- # 4) Set the deploy parameters. -- -- Several parameters can be set to deploy cargo: -- -- * @{#AI_CARGO_DISPATCHER.SetDeployRadius}(): Sets or randomizes the deploy location for the carrier around the cargo coordinate in a radius defined an outer and an optional inner radius. -- * @{#AI_CARGO_DISPATCHER.SetDeploySpeed}(): Set the speed or randomizes the speed in km/h to deploy the cargo. -- --- ## 5. Set the home zone when there isn't any more cargo to pickup. +-- # 5) Set the home zone when there isn't any more cargo to pickup. -- -- A home zone can be specified to where the Carriers will move when there isn't any cargo left for pickup. -- Use @{#AI_CARGO_DISPATCHER.SetHomeZone}() to specify the home zone. @@ -74,7 +382,7 @@ AI_CARGO_DISPATCHER = { ClassName = "AI_CARGO_DISPATCHER", SetCarrier = nil, - DeployZonesSet = nil, + DeployZoneSet = nil, AI_Cargo = {}, PickupCargo = {} } @@ -114,21 +422,25 @@ function AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo ) self:AddTransition( "Monitoring", "Stop", "Idle" ) - self:AddTransition( "*", "Pickup", "*" ) - self:AddTransition( "*", "Loading", "*" ) - self:AddTransition( "*", "Loaded", "*" ) + self:AddTransition( "Monitoring", "Pickup", "Monitoring" ) + self:AddTransition( "Monitoring", "Load", "Monitoring" ) + self:AddTransition( "Monitoring", "Loading", "Monitoring" ) + self:AddTransition( "Monitoring", "Loaded", "Monitoring" ) + self:AddTransition( "Monitoring", "PickedUp", "Monitoring" ) - self:AddTransition( "*", "Deploy", "*" ) - self:AddTransition( "*", "Unloading", "*" ) - self:AddTransition( "*", "Unloaded", "*" ) + self:AddTransition( "Monitoring", "Transport", "Monitoring" ) + + self:AddTransition( "Monitoring", "Deploy", "Monitoring" ) + self:AddTransition( "Monitoring", "Unload", "Monitoring" ) + self:AddTransition( "Monitoring", "Unloading", "Monitoring" ) + self:AddTransition( "Monitoring", "Unloaded", "Monitoring" ) + self:AddTransition( "Monitoring", "Deployed", "Monitoring" ) - self:AddTransition( "*", "Home", "*" ) - self:AddTransition( "*", "RTB", "*" ) --FF - self:AddTransition( "*", "BackHome", "*" ) --FF + self:AddTransition( "Monitoring", "Home", "Monitoring" ) self.MonitorTimeInterval = 30 - self.DeployInnerRadius = 200 - self.DeployOuterRadius = 500 + self.DeployRadiusInner = 200 + self.DeployRadiusOuter = 500 self.PickupCargo = {} self.CarrierHome = {} @@ -146,23 +458,25 @@ end --- Creates a new AI_CARGO_DISPATCHER object. -- @param #AI_CARGO_DISPATCHER self --- @param Core.Set#SET_GROUP SetCarriers --- @param Core.Set#SET_CARGO SetCargos --- @param Core.Set#SET_ZONE DeployZonesSet +-- @param Core.Set#SET_GROUP SetCarrier +-- @param Core.Set#SET_CARGO SetCargo +-- @param Core.Set#SET_ZONE PickupZoneSet +-- @param Core.Set#SET_ZONE DeployZoneSet -- @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() +-- DeployZoneSet = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() -- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) -- -function AI_CARGO_DISPATCHER:NewWithZones( SetCarriers, SetCargos, DeployZonesSet ) +function AI_CARGO_DISPATCHER:NewWithZones( SetCarriers, SetCargos, PickupZoneSet, DeployZoneSet ) local self = AI_CARGO_DISPATCHER:New( SetCarriers, SetCargos ) -- #AI_CARGO_DISPATCHER - self.DeployZonesSet = DeployZonesSet + self.PickupZoneSet = PickupZoneSet + self.DeployZoneSet = DeployZoneSet return self end @@ -231,6 +545,19 @@ function AI_CARGO_DISPATCHER:SetHomeBase( HomeBase ) 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. -- There are two aspects that are very important to remember and take into account: @@ -356,51 +683,203 @@ end function AI_CARGO_DISPATCHER:onafterMonitor() for CarrierGroupName, Carrier in pairs( self.SetCarrier:GetSet() ) do - env.info("FF cargo dispatcher carrier group "..CarrierGroupName) - local Carrier = Carrier -- Wrapper.Group#GROUP local AI_Cargo = self.AI_Cargo[Carrier] if not AI_Cargo then - env.info("FF not AI CARGO") -- ok, so this Carrier does not have yet an AI_CARGO handling object... -- let's create one and also declare the Loaded and UnLoaded handlers. self.AI_Cargo[Carrier] = self:AICargo( Carrier, self.SetCargo, self.CombatRadius ) AI_Cargo = self.AI_Cargo[Carrier] - function AI_Cargo.OnAfterPickup( AI_Cargo, Carrier, From, Event, To, Cargo ) - self:Pickup( Carrier, Cargo ) + --- Pickup event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup is routed towards a new pickup Coordinate and a specified Speed. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickup + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Point#COORDINATE Coordinate The coordinate of the pickup location. + -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the pickup Coordinate. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + function AI_Cargo.OnAfterPickup( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, PickupZone ) + self:Pickup( CarrierGroup, Coordinate, Speed, PickupZone ) end - function AI_Cargo.OnAfterLoad( AI_Cargo, Carrier, From, Event, To, Cargo ) - self:Loading( Carrier ) + --- Load event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup has initiated the loading or boarding of cargo within reporting or near range. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoad + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterLoad( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) + self:Load( CarrierGroup, PickupZone ) end - function AI_Cargo.OnAfterLoaded( AI_Cargo, Carrier, From, Event, To, Cargo ) - self:Loaded( Carrier, Cargo ) + --- Loading event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of loading or boarding of a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that this event is triggered repeatedly until all cargo (units) have been boarded into the carrier. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoading + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterBoard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) + self:Loading( CarrierGroup, Cargo, CarrierUnit, PickupZone ) end - function AI_Cargo.OnAfterDeploy( AI_Cargo, Carrier, From, Event, To, Cargo ) - self:Deploy( Carrier, Cargo ) + --- Loaded event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has loaded a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that if more cargo objects were loading or boarding into the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. + -- A CarrierUnit can be part of the larger CarrierGroup. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo loading operation. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterLoaded( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, PickupZone ) + self:Loaded( CarrierGroup, Cargo, CarrierUnit, PickupZone ) + end + + --- PickedUp event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a carrier has picked up all cargo objects into the CarrierGroup. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterPickedUp + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE_AIRBASE PickupZone (optional) The zone from where the cargo is picked up. Note that the zone is optional and may not be provided, but for AI_CARGO_DISPATCHER_AIRBASE there will always be a PickupZone, as the pickup location is an airbase zone. + + function AI_Cargo.OnAfterPickedUp( AI_Cargo, CarrierGroup, From, Event, To, PickupZone ) + self:PickedUp( CarrierGroup, PickupZone ) + self:Transport( CarrierGroup ) + end + + + --- Deploy event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup is routed to a deploy coordinate, to Unload all cargo objects in each CarrierUnit. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeploy + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Point#COORDINATE Coordinate The deploy coordinate. + -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the deploy Coordinate. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterDeploy( AI_Cargo, CarrierGroup, From, Event, To, Coordinate, Speed, DeployZone ) + self:Deploy( CarrierGroup, Coordinate, Speed, DeployZone ) end - function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo ) - self:Unloading( Carrier, Cargo ) + + --- Unload event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup has initiated the unloading or unboarding of cargo. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnload + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterUnload( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:Unloading( Carrier, Cargo, CarrierUnit, DeployZone ) end - function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo ) - self:Unloaded( Carrier, Cargo ) - end + --- UnLoading event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup is in the process of unloading or unboarding of a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that this event is triggered repeatedly until all cargo (units) have been unboarded from the CarrierUnit. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloading + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- FF added BackHome event. - function AI_Cargo.OnAfterBackHome( AI_Cargo, Carrier, From, Event, To) - self:BackHome( Carrier ) + function AI_Cargo.OnAfterUnboard( AI_Cargo, CarrierGroup, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:Unloading( CarrierGroup, Cargo, CarrierUnit, DeployZone ) end + + + --- Unloaded event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierUnit of a CarrierGroup has unloaded a cargo object. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- Note that if more cargo objects were unloading or unboarding from the CarrierUnit, then this event can be triggered multiple times for each different Cargo/CarrierUnit. + -- A CarrierUnit can be part of the larger CarrierGroup. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Cargo.Cargo#CARGO Cargo The cargo object. + -- @param Wrapper.Unit#UNIT CarrierUnit The carrier unit that is executing the cargo unloading operation. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. - -- FF added RTB event. - function AI_Cargo.OnAfterRTB( AI_Cargo, Carrier, From, Event, To, Airbase) - self:RTB( Carrier, Airbase ) - end + function AI_Cargo.OnAfterUnloaded( AI_Cargo, Carrier, From, Event, To, Cargo, CarrierUnit, DeployZone ) + self:Unloaded( Carrier, Cargo, CarrierUnit, DeployZone ) + end + + --- Deployed event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a carrier has deployed all cargo objects from the CarrierGroup. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterDeployed + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Zone#ZONE DeployZone The zone wherein the cargo is deployed. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterDeployed( AI_Cargo, Carrier, From, Event, To, DeployZone ) + self:Deployed( Carrier, DeployZone ) + end + + --- Home event handler OnAfter for AI_CARGO_DISPATCHER. + -- Use this event handler to tailor the event when a CarrierGroup is returning to the HomeZone, after it has deployed all cargo objects from the CarrierGroup. + -- You can use this event handler to post messages to players, or provide status updates etc. + -- If there is no HomeZone is specified, the CarrierGroup will stay at the current location after having deployed all cargo. + -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterHome + -- @param #AI_CARGO_DISPATCHER self + -- @param #string From A string that contains the "*from state name*" when the event was triggered. + -- @param #string Event A string that contains the "*event name*" when the event was triggered. + -- @param #string To A string that contains the "*to state name*" when the event was triggered. + -- @param Wrapper.Group#GROUP CarrierGroup The group object that contains the CarrierUnits. + -- @param Core.Point#COORDINATE Coordinate The home coordinate the Carrier will arrive and stop it's activities. + -- @param #number Speed The velocity in meters per second on which the CarrierGroup is routed towards the home Coordinate. + -- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. + + function AI_Cargo.OnAfterHome( AI_Cargo, Carrier, From, Event, To, Coordinate, Speed, HomeZone ) + self:Home( Carrier, Coordinate, Speed, HomeZone ) + end end -- The Pickup sequence ... @@ -412,6 +891,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor() -- now find the first cargo that is Unloaded local PickupCargo = nil + local PickupZone = nil for CargoName, Cargo in UTILS.spairs( self.SetCargo:GetSet(), function( t, a, b ) return t[a]:GetWeight() < t[b]:GetWeight() end ) do local Cargo = Cargo -- Cargo.Cargo#CARGO @@ -419,81 +899,53 @@ function AI_CARGO_DISPATCHER:onafterMonitor() if Cargo:IsUnLoaded() == true and Cargo:IsDeployed() == false then local CargoCoordinate = Cargo:GetCoordinate() local CoordinateFree = true - for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do - if CarrierPickup:IsAlive() == true then - if CargoCoordinate:Get2DDistance( Coordinate ) <= 25 then - CoordinateFree = false + PickupZone = self.PickupZoneSet and self.PickupZoneSet:IsCoordinateInZone( CargoCoordinate ) + if not self.PickupZoneSet or PickupZone then + for CarrierPickup, Coordinate in pairs( self.PickupCargo ) do + if CarrierPickup:IsAlive() == true then + if CargoCoordinate:Get2DDistance( Coordinate ) <= 100 then + CoordinateFree = false + break + end + else + self.PickupCargo[CarrierPickup] = nil + end + end + if CoordinateFree == true then + -- Check if this cargo can be picked-up by at least one carrier unit of AI_Cargo. + local LargestLoadCapacity = 0 + for _, Carrier in pairs( Carrier:GetUnits() ) do + local LoadCapacity = Carrier:GetCargoBayFreeWeight() + if LargestLoadCapacity < LoadCapacity then + LargestLoadCapacity = LoadCapacity + end + end + -- So if there is aa carrier that has the required load capacity to load the total weight of the cargo, dispatch the carrier. + -- Otherwise break and go to the next carrier. + -- This will skip cargo which is too large to be able to be loaded by carriers + -- and will secure an efficient dispatching scheme. + if LargestLoadCapacity >= Cargo:GetWeight() then + self.PickupCargo[Carrier] = CargoCoordinate + PickupCargo = Cargo break end - else - self.PickupCargo[CarrierPickup] = nil - end - end - if CoordinateFree == true then - -- Check if this cargo can be picked-up by at least one carrier unit of AI_Cargo. - local LargestLoadCapacity = 0 - for _, Carrier in pairs( Carrier:GetUnits() ) do - local LoadCapacity = Carrier:GetCargoBayFreeWeight() - if LargestLoadCapacity < LoadCapacity then - LargestLoadCapacity = LoadCapacity - end - end - -- So if there is aa carrier that has the required load capacity to load the total weight of the cargo, dispatch the carrier. - -- Otherwise break and go to the next carrier. - -- This will skip cargo which is too large to be able to be loaded by carriers - -- and will secure an efficient dispatching scheme. - if LargestLoadCapacity >= Cargo:GetWeight() then - self.PickupCargo[Carrier] = CargoCoordinate - PickupCargo = Cargo - break end end end end if PickupCargo then - self.CarrierHome[Carrier] = nil local PickupCoordinate = PickupCargo:GetCoordinate():GetRandomCoordinateInRadius( self.PickupOuterRadius, self.PickupInnerRadius ) - - 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 + AI_Cargo:Pickup( PickupCoordinate, math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), PickupZone ) break - else - - env.info("FF HomeZone or HomeBase?") if self.HomeZone then - - env.info("FF HomeZone! Really?") if not self.CarrierHome[Carrier] then - env.info("FF Yes!") self.CarrierHome[Carrier] = true - AI_Cargo:__Home( 60, self.HomeZone:GetRandomPointVec2() ) - else - env.info("FF Nope!") + AI_Cargo:__Home( 60, self.HomeZone:GetRandomPointVec2(), math.random( self.PickupMinSpeed, self.PickupMaxSpeed ), self.HomeZone ) end - - elseif self.HomeBase2 then - - env.info("FF HomeBase! Really?") - if not self.CarrierHome[Carrier] then - env.info("FF Yes!") - self.CarrierHome[Carrier] = true - AI_Cargo:__RTB( 1, self.HomeBase ) - else - env.info("FF Nope!") - end - end - end end end @@ -501,7 +953,7 @@ function AI_CARGO_DISPATCHER:onafterMonitor() self:__Monitor( self.MonitorTimeInterval ) end ---- Start Handler OnBefore for AI_CARGO_DISPATCHER +--- Start event handler OnBefore for AI_CARGO_DISPATCHER -- @function [parent=#AI_CARGO_DISPATCHER] OnBeforeStart -- @param #AI_CARGO_DISPATCHER self -- @param #string From @@ -509,7 +961,7 @@ end -- @param #string To -- @return #boolean ---- Start Handler OnAfter for AI_CARGO_DISPATCHER +--- Start event handler OnAfter for AI_CARGO_DISPATCHER -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterStart -- @param #AI_CARGO_DISPATCHER self -- @param #string From @@ -529,7 +981,7 @@ function AI_CARGO_DISPATCHER:onafterStart( From, Event, To ) self:__Monitor( -1 ) end ---- Stop Handler OnBefore for AI_CARGO_DISPATCHER +--- Stop event handler OnBefore for AI_CARGO_DISPATCHER -- @function [parent=#AI_CARGO_DISPATCHER] OnBeforeStop -- @param #AI_CARGO_DISPATCHER self -- @param #string From @@ -537,7 +989,7 @@ end -- @param #string To -- @return #boolean ---- Stop Handler OnAfter for AI_CARGO_DISPATCHER +--- Stop event handler OnAfter for AI_CARGO_DISPATCHER -- @function [parent=#AI_CARGO_DISPATCHER] OnAfterStop -- @param #AI_CARGO_DISPATCHER self -- @param #string From @@ -554,30 +1006,6 @@ end -- @param #number Delay - ---- Loaded Handler OnAfter for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] OnAfterLoaded --- @param #AI_CARGO_DISPATCHER self --- @param #string From From state. --- @param #string Event Event. --- @param #string To To state. --- @param Wrapper.Group#GROUP Carrier Carrier object. --- @param Cargo.Cargo#CARGO Cargo Cargo object. - ---- Unloaded Handler OnAfter for AI_CARGO_DISPATCHER --- @function [parent=#AI_CARGO_DISPATCHER] OnAfterUnloaded --- @param #AI_CARGO_DISPATCHER self --- @param #string From --- @param #string Event --- @param #string To --- @param Wrapper.Group#GROUP Carrier --- @param Cargo.Cargo#CARGO Cargo - - - - - - --- Make a Carrier run for a cargo deploy action after the cargo has been loaded, by default. -- @param #AI_CARGO_DISPATCHER self -- @param From @@ -586,23 +1014,14 @@ end -- @param Wrapper.Group#GROUP Carrier -- @param Cargo.Cargo#CARGO Cargo -- @return #AI_CARGO_DISPATCHER -function AI_CARGO_DISPATCHER:OnAfterLoaded( From, Event, To, Carrier, Cargo ) - +function AI_CARGO_DISPATCHER:onafterTransport( From, Event, To, Carrier, Cargo ) - if self.DeployZonesSet then - - 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 ) ) - - end - - if self.DeployAirbasesSet then - + if self.DeployZoneSet 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 ) ) + local DeployZone = self.DeployZoneSet:GetRandomZone() + + local DeployCoordinate = DeployZone:GetCoordinate():GetRandomCoordinateInRadius( self.DeployOuterRadius, self.DeployInnerRadius ) + self.AI_Cargo[Carrier]:Deploy( DeployCoordinate, math.random( self.DeployMinSpeed, self.DeployMaxSpeed ), DeployZone ) end end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index 1612ce2c7..fffaaf90f 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua @@ -1,4 +1,4 @@ ---- **AI** -- Models the intelligent transportation of infantry and other cargo using APCs. +--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using APCs. -- -- **Features:** -- @@ -28,9 +28,18 @@ --- A dynamic cargo transportation capability for AI groups. -- -- Armoured Personnel APCs (APC), Trucks, Jeeps and other carrier equipment can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI\_CARGO\_DISPATCHER\_APC module uses the @{Cargo} capabilities within the MOOSE framework. --- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_APC object recognize the cargo. --- Please consult the @{Cargo} module for more information. +-- +-- The AI_CARGO_DISPATCHER_APC module is derived from the AI_CARGO_DISPATCHER module. +-- +-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_APC class, it is recommended that you +-- **first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** +-- +-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! +-- +-- On top, the AI_CARGO_DISPATCHER_APC class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. +-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. +-- -- -- ## 1. AI\_CARGO\_DISPATCHER\_APC constructor -- @@ -88,22 +97,23 @@ AI_CARGO_DISPATCHER_APC = { --- Creates a new AI_CARGO_DISPATCHER_APC object. -- @param #AI_CARGO_DISPATCHER_APC self --- @param Core.Set#SET_GROUP SetAPC The collection of APC @{Wrapper.Group}s. --- @param Core.Set#SET_CARGO SetCargo The collection of @{Cargo} derived objects. --- @param Core.Set#SET_ZONE SetDeployZone The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the APCs. +-- @param Core.Set#SET_GROUP APCSet The collection of APC @{Wrapper.Group}s. +-- @param Core.Set#SET_CARGO CargoSet The collection of @{Cargo.Cargo} derived objects. +-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The collection of pickup @{Zone}s, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere. +-- @param Core.Set#SET_ZONE DeployZoneSet The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the APCs. -- @param DCS#Distance CombatRadius The cargo will be unloaded from the APC and engage the enemy if the enemy is within CombatRadius range. The radius is in meters, the default value is 500 meters. -- @return #AI_CARGO_DISPATCHER_APC -- @usage -- -- -- Create a new cargo dispatcher for the set of APCs, with a combatradius of 500. --- SetAPC = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() --- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, 500 ) +-- APCSet = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() +-- CargoSet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- DeployZoneSet = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( APCSet, SCargoSet, nil, DeployZoneSet, 500 ) -- -function AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, CombatRadius ) +function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius ) - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( SetAPC, SetCargo, SetDeployZone ) ) -- #AI_CARGO_DISPATCHER_APC + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( APCSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_APC self.CombatRadius = CombatRadius or 500 @@ -116,7 +126,7 @@ function AI_CARGO_DISPATCHER_APC:New( SetAPC, SetCargo, SetDeployZone, CombatRad end -function AI_CARGO_DISPATCHER_APC:AICargo( APC, SetCargo ) +function AI_CARGO_DISPATCHER_APC:AICargo( APC, CargoSet ) - return AI_CARGO_APC:New( APC, SetCargo, self.CombatRadius ) + return AI_CARGO_APC:New( APC, CargoSet, self.CombatRadius ) end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua index 300fef9ae..1df08d56f 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua @@ -1,5 +1,10 @@ --- **AI** -- (R2.4) - Models the intelligent transportation of infantry and other cargo using Planes. -- +-- **Features:** +-- +-- * The airplanes will fly towards the pickup airbases to pickup the cargo. +-- * The airplanes will fly towards the deploy airbases to deploy the cargo. +-- -- === -- -- ### Author: **FlightControl** @@ -16,9 +21,17 @@ --- 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. --- Please consult the @{Cargo} module for more information. +-- +-- The AI_CARGO_DISPATCHER_AIRPLANE module is derived from the AI_CARGO_DISPATCHER module. +-- +-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_AIRPLANE class, it is recommended that you +-- **first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** +-- +-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! +-- +-- On top, the AI_CARGO_DISPATCHER_AIRPLANE class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. +-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. -- -- -- @@ -29,23 +42,25 @@ AI_CARGO_DISPATCHER_AIRPLANE = { --- Creates a new AI_CARGO_DISPATCHER_AIRPLANE object. -- @param #AI_CARGO_DISPATCHER_AIRPLANE self --- @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. +-- @param Core.Set#SET_GROUP AirplaneSet Set of cargo transport airplanes. +-- @param Core.Set#SET_CARGO CargoSet Set of cargo, which is supposed to be transported. +-- @param Core.Zone#SET_ZONE PickupZoneSet Set of zone airbases where the cargo has to be picked up. +-- @param Core.Zone#SET_ZONE DeployZoneSet Set of zone airbases where the cargo is deployed. Choice for each cargo is random. -- @return #AI_CARGO_DISPATCHER_AIRPLANE self -- @usage -- -- -- Create a new cargo dispatcher --- 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 ) +-- AirplaneSet = SET_GROUP:New():FilterPrefixes( "Airplane" ):FilterStart() +-- CargoSet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- PickupZoneSet = SET_AIRBASE:New() +-- DeployZoneSet = SET_AIRBASE:New() +-- PickupZoneSet:AddZone( ZONE_AIRBASE:New( "Gudauta", AIRBASE:FindByName( AIRBASE.Caucasus.Gudauta ), 3000 ) ) +-- DeployZoneSet:AddZone( ZONE_AIRBASE:New( "Sochi", AIRBASE:FindByName( AIRBASE.Caucasus.Sochi_Adler ), 3000 ) ) +-- AICargoDispatcher = AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) -- -function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplanes, SetCargos, PickupAirbasesSet, DeployAirbasesSet ) +function AI_CARGO_DISPATCHER_AIRPLANE:New( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithAirbases( SetAirplanes, SetCargos, PickupAirbasesSet, DeployAirbasesSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( AirplaneSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_AIRPLANE self:SetDeploySpeed( 200, 150 ) self:SetPickupSpeed( 200, 150 ) @@ -55,7 +70,7 @@ function AI_CARGO_DISPATCHER_AIRPLANE:New( SetAirplanes, SetCargos, PickupAirbas return self end -function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, SetCargo ) +function AI_CARGO_DISPATCHER_AIRPLANE:AICargo( Airplane, CargoSet ) - return AI_CARGO_AIRPLANE:New( Airplane, SetCargo ) + return AI_CARGO_AIRPLANE:New( Airplane, CargoSet ) end diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua index 26b5d9aab..69beb31f4 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua @@ -1,7 +1,12 @@ ---- **AI** -- Models the intelligent transportation of infantry and other cargo using Helicopters. --- --- The @{#AI_CARGO_DISPATCHER_HELICOPTER} classes implements the dynamic dispatching of cargo transportation tasks for helicopters. +--- **AI** -- (2.4) - Models the intelligent transportation of infantry and other cargo using Helicopters. -- +-- **Features:** +-- +-- * The helicopters will fly towards the pickup locations to pickup the cargo. +-- * The helicopters will fly towards the deploy zones to deploy the cargo. +-- * Precision deployment as well as randomized deployment within the deploy zones are possible. +-- * Helicopters will orbit the deploy zones when there is no space for landing until the deploy zone is free. +-- -- === -- -- ### Author: **FlightControl** @@ -18,9 +23,18 @@ --- A dynamic cargo handling capability for AI helicopter groups. -- -- Helicopters can be mobilized to intelligently transport infantry and other cargo within the simulation. --- The AI\_CARGO\_DISPATCHER\_HELICOPTER module uses the @{Cargo} capabilities within the MOOSE framework. --- CARGO derived objects must be declared within the mission to make the AI\_CARGO\_DISPATCHER\_HELICOPTER object recognize the cargo. --- Please consult the @{Cargo} module for more information. +-- +-- +-- The AI_CARGO_DISPATCHER_HELICOPTER module is derived from the AI_CARGO_DISPATCHER module. +-- +-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_HELICOPTER class, it is recommended that you +-- **first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!!** +-- +-- Especially to learn how to **Tailor the different cargo handling events**, this will be very useful! +-- +-- On top, the AI_CARGO_DISPATCHER_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- Also ensure that you fully understand how to declare and setup Cargo objects within the MOOSE framework before using this class. +-- CARGO derived objects must be declared within the mission to make the AI_CARGO_DISPATCHER_HELICOPTER object recognize the cargo. -- -- --- -- @@ -88,21 +102,22 @@ AI_CARGO_DISPATCHER_HELICOPTER = { --- Creates a new AI_CARGO_DISPATCHER_HELICOPTER object. -- @param #AI_CARGO_DISPATCHER_HELICOPTER self --- @param Core.Set#SET_GROUP SetHelicopter The collection of Helicopter @{Wrapper.Group}s. --- @param Core.Set#SET_CARGO SetCargo The collection of @{Cargo} derived objects. --- @param Core.Set#SET_ZONE SetDeployZones The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the Helicopters. +-- @param Core.Set#SET_GROUP HelicopterSet The collection of Helicopter @{Wrapper.Group}s. +-- @param Core.Set#SET_CARGO CargoSet The collection of @{Cargo.Cargo} derived objects. +-- @param Core.Set#SET_ZONE PickupZoneSet (optional) The collection of pickup @{Zone}s, which are used to where the cargo can be picked up by the APCs. If nil, then cargo can be picked up everywhere. +-- @param Core.Set#SET_ZONE DeployZoneSet The collection of deploy @{Zone}s, which are used to where the cargo will be deployed by the Helicopters. -- @return #AI_CARGO_DISPATCHER_HELICOPTER -- @usage -- -- -- Create a new cargo dispatcher --- SetHelicopter = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() --- SetCargo = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() --- SetDeployZone = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- AICargoDispatcher = AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo ) +-- HelicopterSet = SET_GROUP:New():FilterPrefixes( "Helicopter" ):FilterStart() +-- CargoSet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- DeployZoneSet = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- AICargoDispatcher = AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, SetCargo, nil, DeployZoneSet ) -- -function AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo, SetDeployZones ) +function AI_CARGO_DISPATCHER_HELICOPTER:New( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) - local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( SetHelicopter, SetCargo, SetDeployZones ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:NewWithZones( HelicopterSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) -- #AI_CARGO_DISPATCHER_HELICOPTER self:SetDeploySpeed( 200, 150 ) self:SetPickupSpeed( 200, 150 ) @@ -112,8 +127,8 @@ function AI_CARGO_DISPATCHER_HELICOPTER:New( SetHelicopter, SetCargo, SetDeployZ return self end -function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, SetCargo ) +function AI_CARGO_DISPATCHER_HELICOPTER:AICargo( Helicopter, CargoSet ) - return AI_CARGO_HELICOPTER:New( Helicopter, SetCargo ) + return AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) end diff --git a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua index 46cf934a5..11d32053b 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Helicopter.lua @@ -1,4 +1,4 @@ ---- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo). +--- **AI** -- (R2.4) - Models the intelligent transportation of infantry (cargo). -- -- === -- @@ -13,7 +13,34 @@ -- @extends Core.Fsm#FSM_CONTROLLABLE ---- # AI\_CARGO\_TROOPS class, extends @{Core.Fsm#FSM_CONTROLLABLE} +--- Brings a dynamic cargo handling capability for an AI helicopter group. +-- +-- Helicopter carriers can be mobilized to intelligently transport infantry and other cargo within the simulation. +-- +-- The AI_CARGO_HELICOPTER class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- @{Cargo.Cargo} must be declared within the mission to make the AI_CARGO_HELICOPTER object recognize the cargo. +-- Please consult the @{Cargo.Cargo} module for more information. +-- +-- ## Cargo pickup. +-- +-- Using the @{#AI_CARGO_HELICOPTER.Pickup}() method, you are able to direct the helicopters towards a point on the battlefield to board/load the cargo at the specific coordinate. +-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! +-- +-- ## Cargo deployment. +-- +-- Using the @{#AI_CARGO_HELICOPTER.Deploy}() method, you are able to direct the helicopters towards a point on the battlefield to unboard/unload the cargo at the specific coordinate. +-- Ensure that the landing zone is horizontally flat, and that trees cannot be found in the landing vicinity, or the helicopters won't land or will even crash! +-- +-- ## Infantry health. +-- +-- When infantry is unboarded from the APCs, the infantry is actually respawned into the battlefield. +-- As a result, the unboarding infantry is very _healthy_ every time it unboards. +-- This is due to the limitation of the DCS simulator, which is not able to specify the health of new spawned units as a parameter. +-- However, infantry that was destroyed when unboarded, won't be respawned again. Destroyed is destroyed. +-- As a result, there is some additional strength that is gained when an unboarding action happens, but in terms of simulation balance this has +-- marginal impact on the overall battlefield simulation. Fortunately, the firing strength of infantry is limited, and thus, respacing healthy infantry every +-- time is not so much of an issue ... +-- -- -- === -- @@ -21,7 +48,6 @@ AI_CARGO_HELICOPTER = { ClassName = "AI_CARGO_HELICOPTER", Coordinate = nil, -- Core.Point#COORDINATE, - Helicopter_Cargo = {}, } AI_CARGO_QUEUE = {} @@ -33,10 +59,8 @@ AI_CARGO_QUEUE = {} -- @return #AI_CARGO_HELICOPTER function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) - local self = BASE:Inherit( self, FSM_CONTROLLABLE:New() ) -- #AI_CARGO_HELICOPTER + local self = BASE:Inherit( self, AI_CARGO:New( Helicopter, CargoSet ) ) -- #AI_CARGO_HELICOPTER - self.CargoSet = CargoSet -- Cargo.CargoGroup#CARGO_GROUP - self.Zone = ZONE_GROUP:New( Helicopter:GetName(), Helicopter, 300 ) self:SetStartState( "Unloaded" ) @@ -46,17 +70,17 @@ function AI_CARGO_HELICOPTER:New( Helicopter, CargoSet ) self:AddTransition( { "Unloaded", "Loading" }, "Load", "Boarding" ) self:AddTransition( "Boarding", "Board", "Boarding" ) - self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "Boarding", "Loaded", "Boarding" ) + self:AddTransition( "Boarding", "PickedUp", "Loaded" ) self:AddTransition( "Loaded", "Unload", "Unboarding" ) self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) - self:AddTransition( "Unboarding", "Unloaded", "Unloaded" ) + self:AddTransition( "Unboarding", "Unloaded", "Unboarding" ) + self:AddTransition( "Unboarding", "Deployed", "Unloaded" ) self:AddTransition( "*", "Landed", "*" ) self:AddTransition( "*", "Queue", "*" ) self:AddTransition( "*", "Orbit" , "*" ) - self:AddTransition( "*", "Home" , "*" ) - self:AddTransition( "*", "RTB" , "*" ) - self:AddTransition( "*", "BackHome" , "*" ) + self:AddTransition( "*", "Home" , "*" ) self:AddTransition( "*", "Destroyed", "Destroyed" ) @@ -235,18 +259,15 @@ 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() + self:Load( self.PickupZone ) self.RoutePickup = false self.Relocating = true end end - if self.RouteDeploy == true then - local height=Helicopter:GetHeight( true ) - local velocity=Helicopter:GetVelocityKMH() - env.info(string.format("FF helo in air %s, height = %d m, velocity = %d km/h", tostring(Helicopter:InAir()), height, velocity)) - if height <= 10 and velocity < 10 then - self:Unload( true ) + if self.RouteDeploy == true then + if Helicopter:GetHeight( true ) <= 5 and Helicopter:GetVelocityKMH() < 10 then + self:Unload( self.DeployZone ) self.RouteDeploy = false self.Transporting = false self.Relocating = false @@ -264,7 +285,7 @@ end -- @param To -- @param Core.Point#COORDINATE Coordinate -- @param #number Speed -function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed ) +function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) local HelicopterInZone = false @@ -273,7 +294,7 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina local Distance = Coordinate:DistanceFromPointVec2( Helicopter:GetCoordinate() ) if Distance > 2000 then - self:__Queue( -10, Coordinate ) + self:__Queue( -10, Coordinate, Speed, DeployZone ) else local ZoneFree = true @@ -322,8 +343,12 @@ function AI_CARGO_HELICOPTER:onafterQueue( Helicopter, From, Event, To, Coordina -- Now route the helicopter Helicopter:Route( Route, 0 ) + + -- Keep the DeployZone, because when the helo has landed, we want to provide the DeployZone to the mission designer as part of the Unloaded event. + self.DeployZone = DeployZone + else - self:__Queue( -10, Coordinate ) + self:__Queue( -10, Coordinate, Speed, DeployZone ) end end else @@ -378,127 +403,8 @@ function AI_CARGO_HELICOPTER:onafterOrbit( Helicopter, From, Event, To, Coordina end ---- On Before event Load. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param #string From From state. --- @param #string Event Event --- @param #string To To state. -function AI_CARGO_HELICOPTER:onbeforeLoad( Helicopter, From, Event, To) - local Boarding = false - - if Helicopter and Helicopter:IsAlive() then - - self.BoardingCount = 0 - - if Helicopter and Helicopter:IsAlive() then - for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do - local HelicopterUnit = HelicopterUnit -- Wrapper.Unit#UNIT - local CargoBayFreeWeight = HelicopterUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - - for _, Cargo in pairs( self.CargoSet:GetSet() ) do - local Cargo = Cargo -- Cargo.Cargo#CARGO - self:F( { IsUnLoaded = Cargo:IsUnLoaded() } ) - if Cargo:IsUnLoaded() and not Cargo:IsDeployed() then - if Cargo:IsInLoadRadius( HelicopterUnit:GetCoordinate() ) then - self:F( { "In radius", HelicopterUnit:GetName() } ) - - local CargoWeight = Cargo:GetWeight() - - -- 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, HelicopterUnit ) - self.Helicopter_Cargo[HelicopterUnit] = Cargo - Boarding = true - break - end - end - end - end - end - end - end - - return Boarding - -end - ---- On after Board 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. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param Wrapper.Unit#UNIT HelicopterUnit -function AI_CARGO_HELICOPTER:onafterBoard( Helicopter, From, Event, To, Cargo, HelicopterUnit ) - self:F( { Helicopter, From, Event, To, Cargo, HelicopterUnit } ) - - if Helicopter and Helicopter:IsAlive() then - self:F({ IsLoaded = Cargo:IsLoaded() } ) - if not Cargo:IsLoaded() then - self:__Board( 10, Cargo, HelicopterUnit ) - else - local CargoBayFreeWeight = HelicopterUnit:GetCargoBayFreeWeight() - self:F({CargoBayFreeWeight=CargoBayFreeWeight}) - 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 CargoWeight = Cargo:GetWeight() - - -- 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, HelicopterUnit ) - self.Helicopter_Cargo[HelicopterUnit] = Cargo - return - end - end - end - end - self:__Loaded( 1, Cargo ) -- Will only be executed when no more cargo is boarded. - end - end - -end - - ---- 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. +--- On after PickedUp event, raised when all cargo has been loaded into the CarrierGroup. -- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter -- @param #string From From state. @@ -506,8 +412,9 @@ end -- @param #string To To state. -- @param Cargo.Cargo#CARGO Cargo Cargo object. -- @return #boolean Cargo is loaded. -function AI_CARGO_HELICOPTER:onafterLoaded( Helicopter, From, Event, To, Cargo ) - self:F( { Helicopter, From, Event, To, Cargo } ) +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO_HELICOPTER:onafterPickedUp( Helicopter, From, Event, To, PickupZone ) + self:F( { Helicopter, From, Event, To } ) if Helicopter and Helicopter:IsAlive() then self.Transporting = true @@ -515,62 +422,8 @@ function AI_CARGO_HELICOPTER:onafterLoaded( Helicopter, From, Event, To, Cargo ) end ---- On after Unload event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter --- @param #string From From state. --- @param #string Event Event. --- @param #boolean Deployed Cargo is deployed. -function AI_CARGO_HELICOPTER:onafterUnload( Helicopter, From, Event, To, Deployed ) - if Helicopter and Helicopter:IsAlive() then - 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 ) - end - end - end - end - - -end - ---- On after Unboard 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. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. -function AI_CARGO_HELICOPTER:onafterUnboard( Helicopter, From, Event, To, Cargo, Deployed ) - - if Helicopter and Helicopter:IsAlive() then - 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 - -end - ---- On before Unloaded event. +--- On after Deployed event. -- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP Helicopter -- @param #string From From state. @@ -579,45 +432,8 @@ end -- @param Cargo.Cargo#CARGO Cargo Cargo object. -- @param #boolean Deployed Cargo is deployed. -- @return #boolean True if all cargo has been unloaded. -function AI_CARGO_HELICOPTER:onbeforeUnloaded( Helicopter, From, Event, To, Cargo, Deployed ) - self:F( { APC, From, Event, To, Cargo:GetName(), Deployed = Deployed } ) - - local AllUnloaded = true - - --Cargo:Regroup() - - if Helicopter and Helicopter:IsAlive() then - for _, HelicopterUnit in pairs( Helicopter:GetUnits() ) do - local IsEmpty = HelicopterUnit:IsCargoEmpty() - self:I({ IsEmpty = IsEmpty }) - if not IsEmpty then - AllUnloaded = false - break - end - end - - if AllUnloaded == true then - if Deployed == true then - self.Helicopter_Cargo = {} - end - self.Helicopter = Helicopter - end - end - - self:F( { AllUnloaded = AllUnloaded } ) - return AllUnloaded - -end - ---- On after Unloaded 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. --- @param Cargo.Cargo#CARGO Cargo Cargo object. --- @param #boolean Deployed Cargo is deployed. -function AI_CARGO_HELICOPTER:onafterUnloaded( Helicopter, From, Event, To, Cargo, Deployed ) +function AI_CARGO_HELICOPTER:onafterDeployed( Helicopter, From, Event, To, DeployZone ) + self:F( { Helicopter, From, Event, To, DeployZone = DeployZone } ) self:Orbit( Helicopter:GetCoordinate(), 50 ) @@ -627,7 +443,7 @@ function AI_CARGO_HELICOPTER:onafterUnloaded( Helicopter, From, Event, To, Cargo AI_CARGO_QUEUE[Helicopter] = nil end, Helicopter ) - + end --- On after Pickup event. @@ -638,15 +454,12 @@ end -- @param To -- @param Core.Point#COORDINATE Coordinate Pickup place. -- @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:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed ) +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil, if there wasn't any PickupZoneSet provided. +function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordinate, Speed, PickupZone ) if Helicopter and Helicopter:IsAlive() ~= nil then - --Helicopter:Activate() - - env.info("FF route pickup") - - Coordinate:MarkToAll("helo pickupcoord") + Helicopter:Activate() self.RoutePickup = true Coordinate.y = math.random( 50, 500 ) @@ -692,7 +505,8 @@ function AI_CARGO_HELICOPTER:onafterPickup( Helicopter, From, Event, To, Coordin -- Now route the helicopter Helicopter:Route( Route, 1 ) - + + self.PickupZone = PickupZone self.Transporting = true end @@ -702,8 +516,8 @@ end -- @param #AI_CARGO_HELICOPTER self -- @param Wrapper.Group#GROUP AICargoHelicopter -- @param Core.Point#COORDINATE Coordinate Coordinate -function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate ) - AICargoHelicopter:__Queue( -10, Coordinate, 100 ) +function AI_CARGO_HELICOPTER:_Deploy( AICargoHelicopter, Coordinate, DeployZone ) + AICargoHelicopter:__Queue( -10, Coordinate, 100, DeployZone ) end --- On after Deploy event. @@ -714,7 +528,7 @@ end -- @param To -- @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 ) +function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordinate, Speed, DeployZone ) if Helicopter and Helicopter:IsAlive() ~= nil then @@ -759,7 +573,7 @@ function AI_CARGO_HELICOPTER:onafterDeploy( Helicopter, From, Event, To, Coordin local Tasks = {} - Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate ) + Tasks[#Tasks+1] = Helicopter:TaskFunction( "AI_CARGO_HELICOPTER._Deploy", self, Coordinate, DeployZone ) Tasks[#Tasks+1] = Helicopter:TaskOrbitCircle( math.random( 30, 100 ), _speed, CoordinateTo:GetRandomCoordinateInRadius( 800, 500 ) ) --Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) @@ -782,8 +596,9 @@ end -- @param Event -- @param To -- @param Core.Point#COORDINATE Coordinate Home place. --- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 80% of max possible speed the unit can go. -function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed ) +-- @param #number Speed Speed in km/h to drive to the pickup coordinate. Default is 50% of max possible speed the unit can go. +-- @param Core.Zone#ZONE HomeZone The zone wherein the carrier will return when all cargo has been transported. This can be any zone type, like a ZONE, ZONE_GROUP, ZONE_AIRBASE. +function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinate, Speed, HomeZone ) if Helicopter and Helicopter:IsAlive() ~= nil then @@ -793,9 +608,9 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Calculate the target route point. - Coordinate.y = math.random( 100, 500 ) + Coordinate.y = math.random( 50, 200 ) - local _speed=Speed or Helicopter:GetSpeedMax()*0.8 + Speed = Speed or Helicopter:GetSpeedMax()*0.5 --- Create a route point of type air. local CoordinateFrom = Helicopter:GetCoordinate() @@ -803,7 +618,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - _speed, + Speed , true ) Route[#Route+1] = WaypointFrom @@ -814,7 +629,7 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat "RADIO", POINT_VEC3.RoutePointType.TurningPoint, POINT_VEC3.RoutePointAction.TurningPoint, - _speed, + Speed , true ) @@ -823,10 +638,11 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat --- Now we're going to do something special, we're going to call a function from a waypoint action at the AIControllable... Helicopter:WayPointInitialize( Route ) - local Tasks = {} + local Tasks = {} + Tasks[#Tasks+1] = Helicopter:TaskLandAtVec2( CoordinateTo:GetVec2() ) Route[#Route].task = Helicopter:TaskCombo( Tasks ) - + Route[#Route+1] = WaypointTo -- Now route the helicopter @@ -836,105 +652,3 @@ function AI_CARGO_HELICOPTER:onafterHome( Helicopter, From, Event, To, Coordinat end - ---- On after RTB event. Route the helicopter from one airport or it's current position to another airbase. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter Cargo helicopter. --- @param From --- @param Event --- @param To --- @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. -function AI_CARGO_HELICOPTER:onafterRTB( Helicopter, From, Event, To, Airbase, Speed) - - if Helicopter and Helicopter:IsAlive() then - - -- Set takeoff type. - local Takeoff = SPAWN.Takeoff.Hot - - -- Get template of group. - local Template = Helicopter:GetTemplate() - - -- Nil check - if Template==nil then - return - 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 Helicopter:GetSpeedMax()*0.8 - ) - ToWaypoint["airdromeId"] = Airbase:GetID() - ToWaypoint["speed_locked"] = true - - -- Task function triggering the arrived event. - local TaskFunction = Helicopter:TaskFunction("AI_CARGO_HELICOPTER._BackHome", self) - - -- Put task function on last waypoint. - Helicopter:SetTaskWaypoint( ToWaypoint, TaskFunction ) - - - -- 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. - Helicopter:RespawnAtCurrentAirbase(Template, Takeoff, false) - - else - - -- From point. - local GroupPoint = Helicopter:GetVec2() - local FromWaypoint = {} - FromWaypoint.x = GroupPoint.x - FromWaypoint.y = GroupPoint.y - FromWaypoint.type = "Turning Point" - FromWaypoint.action = "Turning Point" - FromWaypoint.speed = Helicopter:GetSpeedMax()*0.8 - - -- The two route points. - Points[1] = FromWaypoint - Points[2] = ToWaypoint - - local PointVec3 = Helicopter:GetPointVec3() - Template.x = PointVec3.x - Template.y = PointVec3.z - - Template.route.points = Points - - local GroupSpawned = Helicopter:Respawn(Template) - - end - end -end - ---- Function called when transport is back home and nothing more to do. Triggering the event BackHome. --- @param Wrapper.Group#GROUP Helicopter Cargo helicopter. --- @param #AI_CARGO_HELICOPTER self -function AI_CARGO_HELICOPTER._BackHome(Group, self) - env.info("FF ai cargo helicopter back home task function") - Group:SmokeRed() - --Trigger BackHome event. - self:__BackHome(1) -end - - ---- On after BackHome event. --- @param #AI_CARGO_HELICOPTER self --- @param Wrapper.Group#GROUP Helicopter Cargo helo. --- @param From --- @param Event --- @param To -function AI_CARGO_HELICOPTER:onafterBackHome( Helicopter, From, Event, To ) - env.info("FF ai cargo helicopter back home event") - Helicopter:SmokeRed() -end \ No newline at end of file From 718679b5dd9ddc1468bf200ef8c88edbe39ad4ac Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 7 Sep 2018 16:41:23 +0200 Subject: [PATCH 51/73] Warehouse v0.3.7w --- .../Moose/AI/AI_Cargo_Dispatcher.lua | 4 +- .../Moose/AI/AI_Cargo_Dispatcher_APC.lua | 6 +- .../Moose/Functional/Warehouse.lua | 323 +++++++++++++----- 3 files changed, 240 insertions(+), 93 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua index eb448955f..3f1cb6a9f 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher.lua @@ -471,9 +471,9 @@ end -- DeployZoneSet = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() -- AICargoDispatcher = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo, SetDeployZone ) -- -function AI_CARGO_DISPATCHER:NewWithZones( SetCarriers, SetCargos, PickupZoneSet, DeployZoneSet ) +function AI_CARGO_DISPATCHER:NewWithZones( SetCarrier, SetCargo, PickupZoneSet, DeployZoneSet ) - local self = AI_CARGO_DISPATCHER:New( SetCarriers, SetCargos ) -- #AI_CARGO_DISPATCHER + local self = AI_CARGO_DISPATCHER:New( SetCarrier, SetCargo ) -- #AI_CARGO_DISPATCHER self.PickupZoneSet = PickupZoneSet self.DeployZoneSet = DeployZoneSet diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua index fffaaf90f..ef4708bda 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_APC.lua @@ -109,7 +109,7 @@ AI_CARGO_DISPATCHER_APC = { -- APCSet = SET_GROUP:New():FilterPrefixes( "APC" ):FilterStart() -- CargoSet = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() -- DeployZoneSet = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() --- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( APCSet, SCargoSet, nil, DeployZoneSet, 500 ) +-- AICargoDispatcher = AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, nil, DeployZoneSet, 500 ) -- function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZoneSet, CombatRadius ) @@ -117,8 +117,8 @@ function AI_CARGO_DISPATCHER_APC:New( APCSet, CargoSet, PickupZoneSet, DeployZon self.CombatRadius = CombatRadius or 500 - self:SetDeploySpeed( 70, 120 ) - self:SetPickupSpeed( 70, 120 ) + self:SetDeploySpeed( 120, 70 ) + self:SetPickupSpeed( 120, 70 ) self:SetPickupRadius( 0, 0 ) self:SetDeployRadius( 0, 0 ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 827033e28..bf0d52a17 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -49,7 +49,8 @@ -- @field #table delivered Table holding all delivered requests. Table elements are #boolean. If true, all cargo has been delivered. -- @field #table defending Table holding all defending requests, i.e. self requests that were if the warehouse is under attack. Table elements are of type @{#WAREHOUSE.Pendingitem}. -- @field Core.Zone#ZONE portzone Zone defining the port of a warehouse. This is where naval assets are spawned. --- @field #table shippinglanes Table holding the user defined shipping between warehouses. +-- @field #table shippinglanes Table holding the user defined shipping between warehouses. +-- @field #table offroadpaths Table holding user defined paths from one warehouse to another. -- @field #boolean autodefence When the warehouse is under attack, automatically spawn assets to defend the warehouse. -- @extends Core.Fsm#FSM @@ -257,6 +258,18 @@ -- -- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. -- +-- ## Off Road Connections +-- +-- For ground troops it is also possible to define off road paths from between warehouses if no proper road connection is available or should not be used. +-- +-- An off road path can be defined via the @{#WAREHOUSE.AddOffRoadPath}(*remotewarehouse*, *group*, *oneway*) function, where +-- *remotewarehouse* is the warehouse to which the path leads. +-- The parameter *group* is a late activated template group. The waypoints of this group are used to define the path between the two warehouses. +-- By default, the reverse paths is automatically added to get *from* the remote warehouse to this warehouse unless the parameter *oneway* is set to true. +-- +-- **Note** that if an off road connection is defined between two warehouses this becomes the default path, i.e. even if there is a path *on road* possible +-- this will not be used. +-- -- ## Rail Connections -- -- A rail connection is automatically defined as the closest point on a railway measured from the center of the spawn zone. But only, if the distance is less than 3 km. @@ -267,7 +280,7 @@ -- -- ## Air Connections -- --- In order to use airborne assets, a warehouse needs to have an associated airbase. This can be an airdrome or a FARP/HELOPAD. +-- In order to use airborne assets, a warehouse needs to have an associated airbase. This can be an airdrome, a FARP/HELOPAD or a ship. -- -- If there is an airbase within 3 km range of the warehouse it is automatically set as the associated airbase. A user can set an airbase manually -- with the @{#WAREHOUSE.SetAirbase} function. Keep in mind, that sometimes, ground units need to walk/drive from the spawn zone to the airport @@ -287,11 +300,13 @@ -- -- ### Defining Shipping Lanes -- --- A shipping lane between to warehouses can be defined by the @{#WAREHOUSE.AddShippingLane}(*remotewarehouse*, *group*) function. The first parameter *remotewarehouse* +-- A shipping lane between to warehouses can be defined by the @{#WAREHOUSE.AddShippingLane}(*remotewarehouse*, *group*, *oneway*) function. The first parameter *remotewarehouse* -- is the warehouse which should be connected to the present warehouse. -- -- The parameter *group* should be a late activated group defined in the mission editor. The waypoints of this group are used as waypoints of the shipping lane. -- +-- By default, the reverse lane is automatically added to the remote warehouse. This can be disabled by setting the *oneway* parameter to *false*. +-- -- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_ShippingLane.png) -- -- === @@ -624,12 +639,12 @@ -- warehouse.Batumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) -- warehouse.Berlin:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) -- --- -- Big explosion at the warehose. It has a very nice damage model by the way :) +-- -- Big explosion at the warehouse. It has a very nice damage model by the way :) -- local function DestroyWarehouse() -- warehouse.Batumi.warehouse:GetCoordinate():Explosion(9999) -- end -- --- -- Create and explosion after 30 sec. +-- -- Create an explosion at the warehouse after 30 sec. -- SCHEDULER:New(nil, DestroyWarehouse, {}, 30) -- -- -- These requests should not be processed any more since the warehouse is destroyed. @@ -788,6 +803,7 @@ WAREHOUSE = { defending = {}, portzone = nil, shippinglanes = {}, + offroadpaths = {}, autodefence = false, } @@ -931,7 +947,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.7" +WAREHOUSE.version="0.3.7w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -941,6 +957,7 @@ WAREHOUSE.version="0.3.7" -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- TODO: Added habours as interface for transport to from warehouses? -- TODO: Set ROE for spawned groups. +-- TODO: Add offroad lanes between warehouses if road connection is not available. -- DONE: Add possibility to add active groups. Need to create a pseudo template before destroy. <== Does not seem to be necessary any more. -- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. @@ -1037,7 +1054,7 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. self:AddTransition("Running", "Request", "*") -- Process a request. Only in running mode. self:AddTransition("Attacked", "Request", "*") -- Process a request. Only in running mode. - self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier. + self:AddTransition("*", "Unloaded", "*") -- Cargo has been unloaded from the carrier (unused ==> unnecessary?). self:AddTransition("*", "Arrived", "*") -- Cargo or transport group has arrived. 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. @@ -1051,7 +1068,7 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("Attacked", "Captured", "Running") -- DONE Warehouse was captured by another coalition. It must have been attacked first. self:AddTransition("*", "AirbaseCaptured", "*") -- DONE Airbase was captured by other coalition. self:AddTransition("*", "AirbaseRecaptured", "*") -- DONE Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "*") -- DONE Warehouse was destoryed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("*", "Destroyed", "*") -- DONE Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. ------------------------ --- Pseudo Functions --- @@ -1158,8 +1175,11 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE.Queueitem Request Information table of the request. - --- Triggers the FSM event "Arrived", i.e. when a group has arrived at the destination warehosue. - -- This function should always be called from the receiving and not the sending warehouse because assets are added back to the + --- Triggers the FSM event "Arrived", i.e. when a group has arrived at the destination warehouse. + -- This function should always be called from the sending and not the receiving warehouse. + -- If the group is a cargo asset, it is added to the receiving warehouse. If the group is a transporter it + -- is added to the sending warehouse since carriers are supposed to return to their home warehouse once + -- all cargo was delivered. -- @function [parent=#WAREHOUSE] Arrived -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group Group that has arrived. @@ -1170,7 +1190,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. -- @param Wrapper.Group#GROUP group Group that has arrived. - --- On after "Arrived" event user function. Called when a groups has arrived. + --- On after "Arrived" event user function. Called when a groups has arrived at its destination. -- @function [parent=#WAREHOUSE] OnAfterArrived -- @param #WAREHOUSE self -- @param #string From From state. @@ -1179,7 +1199,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param Wrapper.Group#GROUP group Group that has arrived. - --- Triggers the FSM event "Delivered". A group has been delivered from the warehouse to another warehouse. + --- Triggers the FSM event "Delivered". All (cargo) assets of a request have been delivered to the receiving warehouse. -- @function [parent=#WAREHOUSE] Delivered -- @param #WAREHOUSE self -- @param #WAREHOUSE.Pendingitem request Pending request that was now delivered. @@ -1485,22 +1505,94 @@ function WAREHOUSE:SetPortZone(zone) return self end ---- Add a shipping lane to another warehouse. --- Note that both warehouses must have a port zone defined before a shipping lane can be added. +--- Add a shipping lane from this warehouse to another remote warehouse. +-- Note that both warehouses must have a port zone defined before a shipping lane can be added! -- Shipping lane is taken from the waypoints of a (late activated) template group. So set up a group, e.g. a ship or a helicopter, and place its -- waypoints along the shipping lane you want to add. -- @param #WAREHOUSE self -- @param #WAREHOUSE remotewarehouse The remote warehouse to where the shipping lane is added -- @param Wrapper.Group#GROUP group Waypoints of this group will define the shipping lane between to warehouses. +-- @param #boolean oneway (Optional) If true, the lane can only be used from this warehouse to the other but not other way around. Default false. -- @return #WAREHOUSE self -function WAREHOUSE:AddShippingLane(remotewarehouse, group) +function WAREHOUSE:AddShippingLane(remotewarehouse, group, oneway) -- Check that port zones are defined. if self.portzone==nil or remotewarehouse.portzone==nil then - self:E(self.wid..string.format("ERROR: Sending or receiving warehouse does not have a port zone defined. Adding shipping lane not possible!")) - return + local text=string.format("ERROR: Sending or receiving warehouse does not have a port zone defined. Adding shipping lane not possible!") + self:_ErrorMessage(text, 5) + return self end + local startcoord=self.portzone:GetRandomCoordinate() + local finalcoord=remotewarehouse.portzone:GetRandomCoordinate() + + local lane=self:_NewLane(group,startcoord,finalcoord) + + -- Debug info. Marks along shipping lane. + if self.Debug then + for i=1,#lane do + local coord=lane[i] --Core.Point#COORDINATE + local text=string.format("Shipping lane %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) + coord:MarkToCoalition(text, self.coalition) + end + end + + -- Add shipping lane. + -- TODO: Maybe add multiple lanes as a table and later randomly select one for the actual route. + self.shippinglanes[remotewarehouse.warehouse:GetName()]=lane + + -- Add shipping lane in the opposite direction. + if not oneway then + remotewarehouse:AddShippingLane(self, group, false) + end + + return self +end + + +--- Add an off-road path from this warehouse to another and back. +-- The start and end points are automatically set to one random point in the respective spawn zones of the two warehouses. +-- By default, the reverse path is also added as path from the remote warehouse to this warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE remotewarehouse The remote warehouse to which the path leads. +-- @param Wrapper.Group#GROUP group Waypoints of this group will define the path between to warehouses. +-- @param #boolean oneway (Optional) If true, the path can only be used from this warehouse to the other but not other way around. Default false. +-- @return #WAREHOUSE self +function WAREHOUSE:AddOffRoadPath(remotewarehouse, group, oneway) + + local startcoord=self.spawnzone:GetRandomCoordinate() + local finalcoord=remotewarehouse.spawnzone:GetRandomCoordinate() + + local lane=self:_NewLane(group,startcoord,finalcoord) + + -- Debug info. Marks along shipping lane. + if self.Debug then + for i=1,#lane do + local coord=lane[i] --Core.Point#COORDINATE + local text=string.format("Off road path from %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) + coord:MarkToCoalition(text, self.coalition) + end + end + + -- Add shipping lane. + self.offroadpaths[remotewarehouse.warehouse:GetName()]=lane + + -- Add shipping lane in the opposite direction. + if not oneway then + remotewarehouse:AddOffRoadPath(self, group, false) + end + + return self +end + +--- Create a new path from a template group. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group Group used for extracting the waypoints. +-- @param Core.Point#COORDINATE startcoord First coordinate. +-- @param Core.Point#COORDINATE finalcoord Final coordinate. +-- @return #table Table with route points. +function WAREHOUSE:_NewLane(group, startcoord, finalcoord) + -- Get route from template. local lanepoints=group:GetTemplateRoutePoints() @@ -1513,8 +1605,8 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group) local coordL=COORDINATE:New(laneL.x, 0, laneL.y) -- Figure out which point is closer to the port of this warehouse. - local distF=self.portzone:GetCoordinate():Get2DDistance(coordF) - local distL=self.portzone:GetCoordinate():Get2DDistance(coordL) + local distF=startcoord:Get2DDistance(coordF) + local distL=startcoord:GetCoordinate():Get2DDistance(coordL) -- Add the shipping lane. Need to take care of the wrong "direction". local lane={} @@ -1532,21 +1624,14 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group) end end - -- Debug info. Marks along shipping lane. - if self.Debug then - for i=1,#lane do - local coord=lane[i] --Core.Point#COORDINATE - local text=string.format("Shipping lane %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) - coord:MarkToCoalition(text, self.coalition) - end - end - - -- Add shipping lane. - self.shippinglanes[remotewarehouse.warehouse:GetName()]=lane + -- Add beginning and end. + table.insert(lane, 1, startcoord) + table.insert(lane, #lane, finalcoord) - return self + return lane end + --- Check if the warehouse is running. -- @param #WAREHOUSE self -- @return #boolean If true, the warehouse is running and requests are processed. @@ -1577,7 +1662,7 @@ end --- Check if the warehouse has a road connection to another warehouse. Both warehouses need to be started! -- @param #WAREHOUSE self --- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. +-- @param #WAREHOUSE warehouse The remote warehouse to where the connection is checked. -- @param #boolean markpath If true, place markers of path segments on the F10 map. -- @param #boolean smokepath If true, put green smoke on path segments. -- @return #boolean If true, the two warehouses are connected by road. @@ -1597,7 +1682,7 @@ end --- Check if the warehouse has a railroad connection to another warehouse. Both warehouses need to be started! -- @param #WAREHOUSE self --- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. +-- @param #WAREHOUSE warehouse The remote warehouse to where the connection is checked. -- @param #boolean markpath If true, place markers of path segments on the F10 map. -- @param #boolean smokepath If true, put green smoke on path segments. -- @return #boolean If true, the two warehouses are connected by road. @@ -1617,7 +1702,7 @@ end --- Check if the warehouse has a shipping lane defined to another warehouse. -- @param #WAREHOUSE self --- @param #WAREHOUSE warehouse The remote warehose to where the connection is checked. +-- @param #WAREHOUSE warehouse The remote warehouse to where the connection is checked. -- @param #boolean markpath If true, place markers of path segments on the F10 map. -- @param #boolean smokepath If true, put green smoke on path segments. -- @return #boolean If true, the two warehouses are connected by road. @@ -1637,7 +1722,7 @@ function WAREHOUSE:HasConnectionNaval(warehouse, markpath, smokepath) if shippinglane then return true,1 else - self:_ErrorMessage("No shipping lane!") + self:T2(string.format("No shipping lane defined between warehouse %s and %s!", self.alias, warehouse.alias)) end end @@ -1645,6 +1730,37 @@ function WAREHOUSE:HasConnectionNaval(warehouse, markpath, smokepath) return nil, -1 end +--- Check if the warehouse has an off road path defined to another warehouse. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE warehouse The remote warehouse to where the connection is checked. +-- @param #boolean markpath If true, place markers of path segments on the F10 map. +-- @param #boolean smokepath If true, put green smoke on path segments. +-- @return #boolean If true, the two warehouses are connected by road. +-- @return #number Path length in meters. Negative distance -1 meter indicates no connection. +function WAREHOUSE:HasConnectionOffRoad(warehouse, markpath, smokepath) + + if warehouse then + + -- Self request + if warehouse.warehouse:GetName()==self.warehouse:GetName() then + return true,1 + end + + -- Get shipping lane. + local offroadpath=self.offroadpaths[warehouse.warehouse:GetName()] + + if offroadpath~=nil then + return true,1 + else + self:T2(string.format("No off-road path defined between warehouse %s and %s!", self.alias, warehouse.alias)) + end + + end + + return nil, -1 +end + + --- Get number of assets in warehouse stock. -- @param #WAREHOUSE self -- @param #string Descriptor (Optional) Descriptor return the number of a specifc asset type. See @{#WAREHOUSE.Descriptor} for possible values. @@ -2740,13 +2856,16 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) ------------------------------------------------------------------------------------------------------------------------------------ -- Set of cargo carriers. - local TransportSet = SET_GROUP:New():FilterDeads() + local TransportSet = SET_GROUP:New() -- Pickup and deploy zones/bases. local PickupAirbaseSet = SET_AIRBASE:New():AddAirbase(self.airbase) local DeployAirbaseSet = SET_AIRBASE:New():AddAirbase(Request.airbase) local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) + --local PickupAirbaseSet = SET_AIRBASE:New() + --ZONE_AIRBASE:New(ZoneName,ZoneAirbase,Radius,AirbaseName) + -- Cargo dispatcher. local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER @@ -3131,6 +3250,9 @@ function WAREHOUSE:onafterUnloaded(From, Event, To, group) end --- On after "Arrived" event. Triggered when a group has arrived at its destination warehouse. +-- The routine should be called by the warehouse sending this asset and not by the receiving warehouse. +-- It is checked if this asset is cargo (or self propelled) or transport. If it is cargo it is put into the stock of receiving warehouse. +-- If it is a transporter it is put back into the sending warehouse since transports are supposed to return their home warehouse. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -3143,7 +3265,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) group:SmokeOrange() end - -- Get request from group. + -- Get pending request this group belongs to. local request=self:_GetRequestOfGroup(group, self.pending) if request then @@ -3155,7 +3277,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) warehouse=self end - -- Debug message + -- Debug message. self:_DebugMessage(string.format("Group %s arrived at warehouse %s!", tostring(group:GetName()), warehouse.alias), 5) -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. @@ -3167,23 +3289,6 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) warehouse:__AddAsset(60, group) end - - --[[ - -- Get request from group name. - local request=self:_GetRequestOfGroup(group, self.pending) - - if request then - - -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. - if group:IsGround() and group:GetSpeedMax()>1 then - group:RouteGroundTo(request.warehouse.coordinate, group:GetSpeedMax()*0.3, "Off Road") - end - - -- Move asset from pending queue into new warehouse. - request.warehouse:__AddAsset(60, group) - - end - ]] end @@ -3535,7 +3640,7 @@ end -- Routing functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Route ground units to destination. +--- Route ground units to destination. ROE is set to return fire and alarm state to green. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The ground group to be routed -- @param #WAREHOUSE.Queueitem request The request for this group. @@ -3547,19 +3652,50 @@ function WAREHOUSE:_RouteGround(group, request) -- Set speed to 70% of max possible. local _speed=group:GetSpeedMax()*0.7 - -- Waypoints for road-to-road connection. - local Waypoints, canroad = group:TaskGroundOnRoad(request.warehouse.road, _speed, "Off Road", false, self.road) + -- Route waypoints. + local Waypoints={} - -- First waypoint = current position of the group. - local FromWP=group:GetCoordinate():WaypointGround(_speed, "Off Road") - table.insert(Waypoints, 1, FromWP) + -- Check if an off road path has been defined. + local hasoffroad=self:HasConnectionOffRoad(request.warehouse, self.Debug) - -- Final coordinate. - local ToWP=request.warehouse.spawnzone:GetRandomCoordinate():WaypointGround(_speed, "Off Road") - table.insert(Waypoints, #Waypoints+1, ToWP) + if hasoffroad then + + -- Get off road path to remote warehouse. + local path=self.offroadpaths[request.warehouse.warehouse:GetName()] + + -- Loop over user defined shipping lanes. + for i=1,#path do + + -- Shortcut and coordinate intellisense. + local coord=path[i] --Core.Point#COORDINATE + + -- Get waypoint for coordinate. + local Waypoint=coord:WaypointGround(_speed, "Off Road") + + -- Add waypoint to route. + table.insert(Waypoints, Waypoint) + end + + else + + -- Waypoints for road-to-road connection. + Waypoints = group:TaskGroundOnRoad(request.warehouse.road, _speed, "Off Road", false, self.road) + + -- First waypoint = current position of the group. + local FromWP=group:GetCoordinate():WaypointGround(_speed, "Off Road") + table.insert(Waypoints, 1, FromWP) + + -- Final coordinate. + local ToWP=request.warehouse.spawnzone:GetRandomCoordinate():WaypointGround(_speed, "Off Road") + table.insert(Waypoints, #Waypoints+1, ToWP) + + end -- Task function triggering the arrived event. - local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) + --local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) + + -- Task function triggering the arrived event at the last waypoint. + local TaskFunction = self:_SimpleTaskFunction("warehouse:_ArrivedSimple", group) -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] @@ -3574,7 +3710,7 @@ function WAREHOUSE:_RouteGround(group, request) end end ---- Route naval units along user defined shipping lanes to destination warehouse. +--- Route naval units along user defined shipping lanes to destination warehouse. ROE is set to return fire. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The naval group to be routed -- @param #WAREHOUSE.Queueitem request The request for this group. @@ -3601,7 +3737,6 @@ function WAREHOUSE:_RouteNaval(group, request) local coord=lane[i] --Core.Point#COORDINATE -- Get waypoint for coordinate. - -- TODO: Might need optimization for Naval. local Waypoint=coord:WaypointGround(_speed) -- Add waypoint to route. @@ -3631,6 +3766,7 @@ end --- Route the airplane from one airbase another. Activates uncontrolled aircraft and sets ROE/ROT for ferry flights. +-- ROE is set to return fire and ROT to passive defence. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP Aircraft Airplane group to be routed. function WAREHOUSE:_RouteAir(aircraft) @@ -3709,7 +3845,7 @@ end -- Event handler functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Arrived event if an air unit/group arrived at its destination. +--- Arrived event if an air unit/group arrived at its destination. This can be an engine shutdown or a landing event. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data table. function WAREHOUSE:_OnEventArrived(EventData) @@ -3735,7 +3871,7 @@ function WAREHOUSE:_OnEventArrived(EventData) if self.uid==wid then -- Debug info. - local text=string.format("Air asset group %s arrived at warehouse %s.", group:GetName(), self.alias) + local text=string.format("Air asset group %s from warehouse %s arrived at its destination.", group:GetName(), self.alias) self:_InfoMessage(text) -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. @@ -3807,30 +3943,37 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventLanding(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event landing!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event landing!", self.alias)) if EventData and EventData.IniGroup then local group=EventData.IniGroup + + -- Try to get UIDs from group name. local wid,aid,rid=self:_GetIDsFromGroup(group) - if wid==self.uid then + + -- Check that this group belongs to this warehouse. + if wid~=nil and wid==self.uid then + + -- Debug info. self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) - - -- Get request of this group - local request=self:_GetRequestOfGroup(group, self.pending) - - -- If request is nil, the cargo has been delivered. - -- TODO: I might need to add a delivered table, to be better able to get this right. - if request==nil then + + -- Check if all cargo was delivered. + if self.delivered[rid]==true then -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. if group:GetCategory()==Group.Category.HELICOPTER then if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then + -- Debug message. self:_DebugMessage("Helicopter landed in spawn zone. No pending request. Putting back into stock.") if self.Debug then group:SmokeWhite() end - self:__AddAsset(30, group) + + -- Group arrived. + self:Arrived(group) + --self:__AddAsset(30, group) + end end @@ -4249,8 +4392,12 @@ function WAREHOUSE:_CheckRequestValid(request) -- Check if there is a valid path on road. local hasroad=self:HasConnectionRoad(request.warehouse) - if not hasroad then - self:E("ERROR: Incorrect request. No valid path on road for ground assets!") + + -- Check if there is a valid off road path. + local hasoffroad=self:HasConnectionOffRoad(request.warehouse) + + if not (hasroad or hasoffroad) then + self:E("ERROR: Incorrect request. No valid path on or off road for ground assets!") valid=false end @@ -5395,10 +5542,10 @@ function WAREHOUSE:_Fireworks(coord) end end ---- Info Message. +--- Info Message. Message send to coalition if reports or debug mode activated (and duration > 0). Text self:I(text) added to DCS.log file. -- @param #WAREHOUSE self -- @param #string text The text of the error message. --- @param #number duration Message display duration in seconds. Default 20 sec. +-- @param #number duration Message display duration in seconds. Default 20 sec. If duration is zero, no message is displayed. function WAREHOUSE:_InfoMessage(text, duration) duration=duration or 20 if duration>0 then @@ -5408,10 +5555,10 @@ function WAREHOUSE:_InfoMessage(text, duration) end ---- Debug message. +--- Debug message. Message send to all if debug mode is activated (and duration > 0). Text self:T(text) added to DCS.log file. -- @param #WAREHOUSE self -- @param #string text The text of the error message. --- @param #number duration Message display duration in seconds. Default 20 sec. +-- @param #number duration Message display duration in seconds. Default 20 sec. If duration is zero, no message is displayed. function WAREHOUSE:_DebugMessage(text, duration) duration=duration or 20 if duration>0 then @@ -5420,14 +5567,14 @@ function WAREHOUSE:_DebugMessage(text, duration) self:T(self.wid..text) end ---- Error message. +--- Error message. Message send to all (if duration > 0). Text self:E(text) added to DCS.log file. -- @param #WAREHOUSE self -- @param #string text The text of the error message. --- @param #number duration Message display duration in seconds. Default 20 sec. +-- @param #number duration Message display duration in seconds. Default 20 sec. If duration is zero, no message is displayed. function WAREHOUSE:_ErrorMessage(text, duration) duration=duration or 20 if duration>0 then - MESSAGE:New(text, duration):ToAllIf(self.Debug) + MESSAGE:New(text, duration):ToAllIf() end self:E(self.wid..text) end From dbd358be35feb02a7556e49e61965e6923f7696f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sat, 8 Sep 2018 23:50:11 +0200 Subject: [PATCH 52/73] Warehosue v0.3.8 Added AAA, SAM, UAV general attributes. Added Quantity enumerator. Updated to new dispatcher API (still WIP). Improved handling for immobile groups. Fixed some bugs. --- Moose Development/Moose/Core/Zone.lua | 10 +- .../Moose/Functional/Warehouse.lua | 701 ++++++++++++------ 2 files changed, 459 insertions(+), 252 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index a7a7f7a4f..295adabb8 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1610,16 +1610,16 @@ do -- ZONE_AIRBASE --- Constructor to create a ZONE_AIRBASE instance, taking the zone name, a zone @{Wrapper.Airbase#AIRBASE} and a radius. -- @param #ZONE_AIRBASE self - -- @param #string ZoneName Name of the zone. - -- @param Wrapper.Airbase#AIRBASE ZoneAirbase The @{Wrapper.Airbase} as the center of the zone. - -- @param DCS#Distance Radius The radius of the zone. + -- @param #string AirbaseName Name of the airbase. + -- @param DCS#Distance Radius (Optional)The radius of the zone in meters. Default 4000 meters. -- @return #ZONE_AIRBASE self - function ZONE_AIRBASE:New( AirbaseName ) + function ZONE_AIRBASE:New( AirbaseName, Radius ) + Radius=Radius or 4000 local Airbase = AIRBASE:FindByName( AirbaseName ) - local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), 4000 ) ) + local self = BASE:Inherit( self, ZONE_RADIUS:New( AirbaseName, Airbase:GetVec2(), Radius ) ) self._.ZoneAirbase = Airbase self._.ZoneVec2Cache = self._.ZoneAirbase:GetVec2() diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index bf0d52a17..dc321d0d7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -73,6 +73,7 @@ -- of important supply lines by capturing or destroying warehouses or their associated infrastructure is equally important. -- -- ## What is a warehouse? +-- -- A warehouse is an abstract object represented by a physical (static) building that can hold virtual assets in stock. -- It can (but it must not) be associated with a particular airbase. The associated airbase can be an airdrome, a Helipad/FARP or a ship. -- @@ -80,8 +81,10 @@ -- by themselfs. Once arrived at the requesting warehouse, the assets go into the stock of the requestor and can be activated/deployed when necessary. -- -- ## What assets can be stored? +-- -- Any kind of ground, airborne or naval asset can be stored and are spawned upon request. --- The fact that the assets "live" only virtually in the stock has a positive impact on the game performance. +-- The fact that the assets live only virtually in stock and are put into the game only when needed has a positive impact on the game performance. +-- It also alliviates the problem of limited parking spots at smaller air bases -- -- ## What means of transportation are available? -- Firstly, all mobile assets can be send from warehouse to another on their own. @@ -94,8 +97,13 @@ -- a reasonable degree in DCS at the moment and hence cannot be used yet. -- -- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modelled --- in a realistic way by using the corresponding cargo dispatcher classes, i.e. @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}, --- @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} and @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}. +-- in a realistic way by using the corresponding cargo dispatcher classes, i.e. +-- +-- * @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}, +-- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} and +-- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}. +-- +-- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. -- -- === -- @@ -265,10 +273,16 @@ -- An off road path can be defined via the @{#WAREHOUSE.AddOffRoadPath}(*remotewarehouse*, *group*, *oneway*) function, where -- *remotewarehouse* is the warehouse to which the path leads. -- The parameter *group* is a late activated template group. The waypoints of this group are used to define the path between the two warehouses. --- By default, the reverse paths is automatically added to get *from* the remote warehouse to this warehouse unless the parameter *oneway* is set to true. +-- By default, the reverse paths is automatically added to get *from* the remote warehouse to this warehouse unless the parameter *oneway* is set to *true*. -- -- **Note** that if an off road connection is defined between two warehouses this becomes the default path, i.e. even if there is a path *on road* possible --- this will not be used. +-- this will not be used. +-- +-- Also note that you can define multiple off road connections between two warehouses. If there are multiple paths defined, the connection is chosen randomly. +-- It is also possible to add the same path multiple times. By this you can influence the probability of the chosen path. For example Path_1(A->B) has been +-- added two times while Path_2(A->B) was added only once. Hence, the group will choose Path_1 with a probability of 66.6 % while Path_2 is only chosen with +-- a probability of 33.3 %. +-- -- -- ## Rail Connections -- @@ -305,7 +319,12 @@ -- -- The parameter *group* should be a late activated group defined in the mission editor. The waypoints of this group are used as waypoints of the shipping lane. -- --- By default, the reverse lane is automatically added to the remote warehouse. This can be disabled by setting the *oneway* parameter to *false*. +-- By default, the reverse lane is automatically added to the remote warehouse. This can be disabled by setting the *oneway* parameter to *true*. +-- +-- Similar to off road connections, you can also define multiple shipping lanes between two warehouse ports. If there are multiple lanes defined, one is chosen randomly. +-- It is possible to add the same lane multiple times. By this you can influence the probability of the chosen lane. For example Lane_1(A->B) has been +-- added two times while Lane_2(A->B) was added only once. Therefore, the ships will choose Lane_1 with a probability of 66.6 % while Path_2 is only chosen with +-- a probability of 33.3 %. -- -- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_ShippingLane.png) -- @@ -870,7 +889,7 @@ WAREHOUSE.Descriptor = { CATEGORY="category", } ---- Generalized asset attributes. Can be used to request assets with certain general characteristics. +--- Generalized asset attributes. Can be used to request assets with certain general characteristics. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit. -- @type WAREHOUSE.Attribute -- @field #string AIR_TRANSPORTPLANE Airplane with transport capability. This can be used to transport other assets. -- @field #string AIR_AWACS Airborne Early Warning and Control System. @@ -879,6 +898,7 @@ WAREHOUSE.Descriptor = { -- @field #string AIR_TANKER Airplane which can refuel other aircraft. -- @field #string AIR_TRANSPORTHELO Helicopter with transport capability. This can be used to transport other assets. -- @field #string AIR_ATTACKHELO Attack helicopter. +-- @field #string AIR_UAV Unpiloted Aerial Vehicle, e.g. drones. -- @field #string AIR_OTHER Any airborne unit that does not fall into any other airborne category. -- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets. -- @field #string GROUND_TRUCK Unarmed ground vehicles. @@ -886,6 +906,8 @@ WAREHOUSE.Descriptor = { -- @field #string GROUND_ARTILLERY Artillery assets. -- @field #string GROUND_TANK Tanks (modern or old). -- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. +-- @field #string GROUND_AAA Anti-Aircraft Artillery. +-- @field #string GROUND_SAM Surface-to-Air Missile system or components. -- @field #string GROUND_OTHER Any ground unit that does not fall into any other ground category. -- @field #string NAVAL_AIRCRAFTCARRIER Aircraft carrier. -- @field #string NAVAL_WARSHIP War ship, i.e. cruisers, destroyers, firgates and corvettes. @@ -901,6 +923,7 @@ WAREHOUSE.Attribute = { AIR_TANKER="Air_Tanker", AIR_TRANSPORTHELO="Air_TransportHelo", AIR_ATTACKHELO="Air_AttackHelo", + AIR_UAV="Air_UAV", AIR_OTHER="Air_OtherAir", GROUND_APC="Ground_APC", GROUND_TRUCK="Ground_Truck", @@ -908,6 +931,8 @@ WAREHOUSE.Attribute = { GROUND_ARTILLERY="Ground_Artillery", GROUND_TANK="Ground_Tank", GROUND_TRAIN="Ground_Train", + GROUND_AAA="Ground_AAA", + GROUND_SAM="Ground_SAM", GROUND_OTHER="Ground_OtherGround", NAVAL_AIRCRAFTCARRIER="Naval_AircraftCarrier", NAVAL_WARSHIP="Naval_WarShip", @@ -934,6 +959,21 @@ WAREHOUSE.TransportType = { SELFPROPELLED = "Selfpropelled", } +--- Warehouse quantity enumerator for selecting number of assets, e.g. all, half etc. of what is in stock rather than an absolute number. +-- @type WAREHOUSE.Quantity +-- @field #string ALL All "all" assets currently in stock. +-- @field #string THREEQUARTERS Three quarters "3/4" of assets in stock. +-- @field #string HALF Half "1/2" of assets in stock. +-- @field #string THIRD One third "1/3" of assets in stock. +-- @field #string QUARTER One quarter "1/4" of assets in stock. +WAREHOUSE.Quantity = { + ALL = "all", + THREEQUARTERS = "3/4", + HALF = "1/2", + THIRD = "1/3", + QUARTER = "1/4", +} + --- Warehouse database. Note that this is a global array to have easier exchange between warehouses. -- @type WAREHOUSE.db -- @field #number AssetID Unique ID of each asset. This is a running number, which is increased each time a new asset is added. @@ -947,21 +987,27 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.7w" +WAREHOUSE.version="0.3.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Test mortars! Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. +-- TODO: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. +-- TODO: Test capturing a neutral warehouse. +-- TODO: Make more examples: ARTY, CAP, +-- TODO: Add SAMs and UAVs to generalized attributes. +-- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- TODO: Added habours as interface for transport to from warehouses? --- TODO: Set ROE for spawned groups. --- TODO: Add offroad lanes between warehouses if road connection is not available. --- DONE: Add possibility to add active groups. Need to create a pseudo template before destroy. <== Does not seem to be necessary any more. -- TODO: Write documentation. --- TODO: Handle the case when units of a group die during the transfer. Adjust template?! See Grouping in SPAWN. --- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult! +-- TODO: Handle the case when units of a group die during the transfer. +-- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua! +-- DONE: Add warehouse quantity enumerator. +-- DONE: Test mortars. Immobile units need a transport. +-- DONE: Set ROE for spawned groups. +-- DONE: Add offroad lanes between warehouses if road connection is not available. +-- DONE: Add possibility to add active groups. Need to create a pseudo template before destroy. <== Does not seem to be necessary any more. -- DONE: Add a time stamp when an asset is added to the stock and for requests. -- DONE: How to get a specific request once the cargo is delivered? Make addrequest addasset non FSM function? Callback for requests like in SPAWN? -- DONE: Add autoselfdefence switch and user function. Default should be off. @@ -1047,8 +1093,9 @@ function WAREHOUSE:New(warehouse, alias) -- Add FSM transitions. -- From State --> Event --> To State - self:AddTransition("NotReadyYet", "Load", "NotReadyYet") -- TODO Load the warehouse state. No sure if it should be in stopped state. - self:AddTransition("NotReadyYet", "Start", "Running") -- Start the warehouse. + self:AddTransition("NotReadyYet", "Load", "Loaded") -- TODO Load the warehouse state. No sure if it should be in stopped state. + self:AddTransition("NotReadyYet", "Start", "Running") -- Start the warehouse from scratch. + self:AddTransition("Loaded", "Start", "Running") -- Start the warehouse when loaded from disk. self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. @@ -1068,7 +1115,7 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("Attacked", "Captured", "Running") -- DONE Warehouse was captured by another coalition. It must have been attacked first. self:AddTransition("*", "AirbaseCaptured", "*") -- DONE Airbase was captured by other coalition. self:AddTransition("*", "AirbaseRecaptured", "*") -- DONE Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "*") -- DONE Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("*", "Destroyed", "Destoyed") -- DONE Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. ------------------------ --- Pseudo Functions --- @@ -1523,10 +1570,12 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group, oneway) return self end + -- Initial and final coordinates are random points within the port zones. local startcoord=self.portzone:GetRandomCoordinate() local finalcoord=remotewarehouse.portzone:GetRandomCoordinate() - local lane=self:_NewLane(group,startcoord,finalcoord) + -- Create new lane from waypoints of the template group. + local lane=self:_NewLane(group, startcoord, finalcoord) -- Debug info. Marks along shipping lane. if self.Debug then @@ -1536,14 +1585,21 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group, oneway) coord:MarkToCoalition(text, self.coalition) end end + + -- Name of the remote warehouse. + local remotename=remotewarehouse.warehouse:GetName() + -- Create new table if no shipping lane exists yet. + if self.shippinglanes[remotename]==nil then + self.shippinglanes[remotename]={} + end + -- Add shipping lane. - -- TODO: Maybe add multiple lanes as a table and later randomly select one for the actual route. - self.shippinglanes[remotewarehouse.warehouse:GetName()]=lane + table.insert(self.shippinglanes[remotename], lane) -- Add shipping lane in the opposite direction. if not oneway then - remotewarehouse:AddShippingLane(self, group, false) + remotewarehouse:AddShippingLane(self, group, true) end return self @@ -1560,26 +1616,36 @@ end -- @return #WAREHOUSE self function WAREHOUSE:AddOffRoadPath(remotewarehouse, group, oneway) + -- Initial and final points are random points within the spawn zone. local startcoord=self.spawnzone:GetRandomCoordinate() local finalcoord=remotewarehouse.spawnzone:GetRandomCoordinate() - local lane=self:_NewLane(group,startcoord,finalcoord) + -- Create new path from template group waypoints. + local path=self:_NewLane(group, startcoord, finalcoord) - -- Debug info. Marks along shipping lane. + -- Debug info. Marks along path. if self.Debug then - for i=1,#lane do - local coord=lane[i] --Core.Point#COORDINATE + for i=1,#path do + local coord=path[i] --Core.Point#COORDINATE local text=string.format("Off road path from %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) coord:MarkToCoalition(text, self.coalition) end end - - -- Add shipping lane. - self.offroadpaths[remotewarehouse.warehouse:GetName()]=lane - -- Add shipping lane in the opposite direction. + -- Name of the remote warehouse. + local remotename=remotewarehouse.warehouse:GetName() + + -- Create new table if no shipping lane exists yet. + if self.offroadpaths[remotename]==nil then + self.offroadpaths[remotename]={} + end + + -- Add off road path. + table.insert(self.offroadpaths[remotename], path) + + -- Add off road path in the opposite direction (if not forbidden). if not oneway then - remotewarehouse:AddOffRoadPath(self, group, false) + remotewarehouse:AddOffRoadPath(self, group, true) end return self @@ -1606,7 +1672,7 @@ function WAREHOUSE:_NewLane(group, startcoord, finalcoord) -- Figure out which point is closer to the port of this warehouse. local distF=startcoord:Get2DDistance(coordF) - local distL=startcoord:GetCoordinate():Get2DDistance(coordL) + local distL=startcoord:Get2DDistance(coordL) -- Add the shipping lane. Need to take care of the wrong "direction". local lane={} @@ -1632,6 +1698,20 @@ function WAREHOUSE:_NewLane(group, startcoord, finalcoord) end +--- Check if the warehouse has not been started yet, i.e. is in the state "NotReadyYet". +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse object has been created but the warehouse has not been started yet. +function WAREHOUSE:IsNotReadyYet() + return self:is("NotReadyYet") +end + +--- Check if the warehouse has been loaded from disk via the "Load" event. +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse was loaded from disk. +function WAREHOUSE:IsLoaded() + return self:is("Loaded") +end + --- Check if the warehouse is running. -- @param #WAREHOUSE self -- @return #boolean If true, the warehouse is running and requests are processed. @@ -1653,6 +1733,13 @@ function WAREHOUSE:IsAttacked() return self:is("Attacked") end +--- Check if the warehouse has been destroyed. +-- @param #WAREHOUSE self +-- @return #boolean If true, the warehouse had been destroyed. +function WAREHOUSE:IsDestroyed() + return self:is("Destroyed") +end + --- Check if the warehouse is stopped. -- @param #WAREHOUSE self -- @return #boolean If true, the warehouse is stopped. @@ -1946,8 +2033,8 @@ function WAREHOUSE:onafterStatus(From, Event, To) self:_CheckConquered() -- Print queue. - self:_PrintQueue(self.queue, "Queue waiting - before request") - self:_PrintQueue(self.pending, "Queue pending - before request") + --self:_PrintQueue(self.queue, "Queue waiting - before request") + --self:_PrintQueue(self.pending, "Queue pending - before request") -- Check if requests are valid and remove invalid one. self:_CheckRequestConsistancy(self.queue) @@ -1964,8 +2051,8 @@ function WAREHOUSE:onafterStatus(From, Event, To) end -- Print queue after processing requests. - self:_PrintQueue(self.queue, "Queue waiting - after request") - self:_PrintQueue(self.pending, "Queue pending - after request") + self:_PrintQueue(self.queue, "Queue waiting") + self:_PrintQueue(self.pending, "Queue pending") end @@ -1981,30 +2068,112 @@ 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 + + self:E(string.format("FF 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 function WAREHOUSE:_JobDone() + -- Loop over all pending requests of this warehouse. local done={} for _,request in pairs(self.pending) do local request=request --#WAREHOUSE.Pendingitem - local ncargo=0 - if request.cargogroupset then - ncargo=request.cargogroupset:Count() - end + -- Count number of cargo groups still alive and filter out dead groups. + local ncargo=self:_FilterDead(request.cargogroupset) - local ntransport=0 - if request.transportgroupset then - ntransport=request.transportgroupset:Count() - end + -- 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) - --TODO: Check if any transports, e.g. APCs were not used and are still standing around in the spawn zone. - -- Also planes and helos might not be used? if ncargo==0 and ntransport==0 then + + --------------- + -- Job done! -- + --------------- + + self:E(string.format("Request id=%d done! No more cargo and transport assets alive.", request.uid)) table.insert(done, request) + + elseif ncargo>0 and ntransport==0 and request.ntransport>0 then + + ----------------------------------- + -- Still cargo but no transports -- + ----------------------------------- + + -- Info message. + self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!", self.alias, request.uid, ncargo)) + + -- All transports are dead but there is still cargo left ==> Put cargo back into stock. + for _,group in pairs(request.cargogroupset) do + local group=group --Wrapper.Group#GROUP + group:SmokeRed() + + -- Add all assets back to stock + if group:IsPartlyOrCompletelyInZone(self.spawnzone) then + self:__AddAsset(5, group) + end + + end + + elseif ncargo==0 and ntransport>0 then + + ----------------------------------- + -- No cargo but still transports -- + ----------------------------------- + + -- This is difficult! How do I know if transports were unused? They could also be just on their way back home. + + -- TODO: Handle this case. + if true then + return + end + + -- Info message. + self:_InfoMessage(string.format("Warehouse %s: No cargo assets left for request id=%s. Putting remaining %s transport assets back into warehouse!", self.alias, request.uid, ntransport)) + + -- All transports are dead but there is still cargo left ==> Put cargo back into stock. + for _,group in pairs(request.transportgroupset) do + local group=group --Wrapper.Group#GROUP + group:SmokeRed() + + -- Add all assets back to stock + if group:IsPartlyOrCompletelyInZone(self.spawnzone) then + self:__AddAsset(5, group) + end + + end + end end @@ -2839,16 +3008,15 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) _loadradius=1000 end + --_loadradius=nil + --_nearradius=nil + -- Empty cargo group set. CargoGroups = SET_CARGO:New() -- Add cargo groups to set. for _i,_group in pairs(_spawngroups:GetSetObjects()) do - local group=_group --Wrapper.Group#GROUP - local _wid,_aid,_rid=self:_GetIDsFromGroup(group) - local _alias=self:_alias(group:GetTypeName(),_wid,_aid,_rid) - local cargogroup = CARGO_GROUP:New(_group, _cargotype,_alias,_loadradius,_nearradius) - CargoGroups:AddCargo(cargogroup) + CargoGroups:AddCargo(CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_loadradius,_nearradius)) end ------------------------------------------------------------------------------------------------------------------------------------ @@ -2858,17 +3026,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Set of cargo carriers. local TransportSet = SET_GROUP:New() - -- Pickup and deploy zones/bases. - local PickupAirbaseSet = SET_AIRBASE:New():AddAirbase(self.airbase) - local DeployAirbaseSet = SET_AIRBASE:New():AddAirbase(Request.airbase) - local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) - - --local PickupAirbaseSet = SET_AIRBASE:New() - --ZONE_AIRBASE:New(ZoneName,ZoneAirbase,Radius,AirbaseName) - - -- Cargo dispatcher. - local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER - -- Shortcut to transport assets. local _assetstock=Request.transportassets @@ -2884,160 +3041,147 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Transport assets table. local _transportassets={} - - -- Dependent on transport type, spawn the transports and set up the dispatchers. - if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - ---------------- - --- AIRPLANE --- - ---------------- - - -- Spawn the transport groups. - for i=1,Request.ntransport do + ---------------------------- + -- Spawn Transport Groups -- + ---------------------------- - -- Get stock item. - local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem + -- Spawn the transport groups. + for i=1,Request.ntransport do + + -- Get stock item. + local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem -- Create an alias name with the UIDs for the sending warehouse, asset and request. - local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) - + local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) + + local spawngroup=nil --Wrapper.Group#GROUP + + -- Spawn assets depending on type. + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + -- Spawn plane at airport in uncontrolled state. Will get activated when cargo is loaded. - local spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem, Pending, Parking[_assetitem.uid], true) - - 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) - - Pending.assets[_assetitem.uid]=_assetitem - table.insert(_transportassets,_assetitem) - end + spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem, Pending, Parking[_assetitem.uid], true) + + elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + + -- Spawn helos at airport in controlled state. They need to fly to the spawn zone. + spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem, Pending, Parking[_assetitem.uid], false) + + elseif Request.transporttype==WAREHOUSE.TransportType.APC then + + -- Spawn APCs in spawn zone. + spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.spawnzone) + + elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then + + self:E(self.wid.."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!") + 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 + + -- Check if group was spawned. + if spawngroup then + -- Set state of warehouse so we can retrieve it later. + spawngroup:SetState(spawngroup, "WAREHOUSE", self) - -- Delete spawned items from warehouse stock. - for _,_item in pairs(_transportassets) do - self:_DeleteStockItem(_item) + -- Add group to transportset. + TransportSet:AddGroup(spawngroup) + + -- Add assets to pending request. + Pending.assets[_assetitem.uid]=_assetitem + + -- Add transport assets. + table.insert(_transportassets,_assetitem) end + end + + -- Delete spawned items from warehouse stock. + for _,_item in pairs(_transportassets) do + self:_DeleteStockItem(_item) + end + + -- Add cargo groups to request. + Pending.transportgroupset=TransportSet + + -- Add request to pending queue. + table.insert(self.pending, Pending) + + -- Delete request from queue. + self:_DeleteQueueItem(Request, self.queue) + + ------------------------ + -- Create Dispatchers -- + ------------------------ + + -- Cargo dispatcher. + local CargoTransport --AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER + + if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + + -- Pickup and deloay zones. + local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) + local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) -- Define dispatcher for this task. CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) - + elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - ------------------ - --- HELICOPTER --- - ------------------ + -- Pickup and deloay zones. + --local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) + --local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) - -- Spawn the transport groups. - for i=1,Request.ntransport do - - -- Get stock item. - local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - - -- Create an alias name with the UIDs for the sending warehouse, asset and request. - local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) - - -- Spawn plane at airport in controlled state. They need to fly to the spawn zone. - local spawngroup=self:_SpawnAssetAircraft(_alias,_assetitem, Pending, Parking[_assetitem.uid], 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) - - Pending.assets[_assetitem.uid]=_assetitem - table.insert(_transportassets,_assetitem) - else - self:E(self.wid.."ERROR: spawngroup helo transport does not exist!") - end - end - - -- Delete spawned items from warehouse stock. - for _,_item in pairs(_transportassets) do - self:_DeleteStockItem(_item) - end + -- Pickup and deloay zones. + local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone) + local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportSet, CargoGroups, DeployZoneSet) + CargoTransport = AI_CARGO_DISPATCHER_HELICOPTER:New(TransportSet, CargoGroups, PickupZoneSet, DeployZoneSet) + + -- Home zone. + CargoTransport:SetHomeZone(self.spawnzone) --TODO: Need to check/optimize if/how this works with polygon zones! -- The 20 m inner radius are to ensure that the helo does not land on the warehouse itself in the middle of the default spawn zone. CargoTransport:SetPickupRadius(self.spawnzone:GetRadius(), 20) - CargoTransport:SetDeployRadius(Request.warehouse.spawnzone:GetRadius(), 20) + CargoTransport:SetDeployRadius(Request.warehouse.spawnzone:GetRadius(), 20) - -- Home zone. - CargoTransport:SetHomeZone(self.spawnzone) - - -- Home airbase (not working). - --CargoTransport:SetHomeBase(self.airbase) - elseif Request.transporttype==WAREHOUSE.TransportType.APC then - ----------- - --- APC --- - ----------- - - -- Spawn the transport groups. - for i=1,Request.ntransport do - - -- Get stock item. - local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - - -- Create an alias name with the UIDs for the sending warehouse, asset and request. - local _alias=self:_alias(_assetitem.unittype, self.uid, _assetitem.uid, Request.uid) - - -- Spawn ground asset. - local spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.spawnzone) - - 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) - - Pending.assets[_assetitem.uid]=_assetitem - table.insert(_transportassets,_assetitem) - end - end - - -- Delete spawned items from warehouse stock. - for _,_item in pairs(_transportassets) do - self:_DeleteStockItem(_item) - end + + -- Pickup and deloay zones. + local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone) + local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) -- Define dispatcher for this task. - CargoTransport = AI_CARGO_DISPATCHER_APC:New(TransportSet, CargoGroups, DeployZoneSet, 0) + CargoTransport = AI_CARGO_DISPATCHER_APC:New(TransportSet, CargoGroups, PickupZoneSet, DeployZoneSet, 0) -- Set home zone. CargoTransport:SetHomeZone(self.spawnzone) - elseif Request.transporttype==WAREHOUSE.TransportType.TRAIN then - - self:E(self.wid.."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!") - 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 + --TODO: Need to check/optimize if/how this works with polygon zones! + -- The 20 m inner radius are to ensure that the helo does not land on the warehouse itself in the middle of the default spawn zone. + CargoTransport:SetPickupRadius(self.spawnzone:GetRadius(), 20) + CargoTransport:SetDeployRadius(Request.warehouse.spawnzone:GetRadius(), 20) + else - self:E(self.wid.."ERROR: unknown transport type!") - return + self:E(self.wid.."ERROR: Unknown transporttype!") end - --- Function called when cargo has arrived and was unloaded. function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo) @@ -3050,21 +3194,19 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Get group obejet. local group=Cargo:GetObject() --Wrapper.Group#GROUP - + -- Get warehouse state. local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE - -- Load the cargo in the warehouse. - --Cargo:Load(warehouse.warehouse) - - -- Get warehouse ID of the - local wid=warehouse:_GetIDsFromGroup(group) - - -- Get the receiving warehouse. - local warehouseReceiving=warehouse:_FindWarehouseInDB(wid) + local text=string.format("FF Group %s was unloaded from carrier %s.", tostring(group:GetName()), tostring(Carrier:GetName())) + env.info(text) - -- Trigger Arrived event at the receiving warehouse. - warehouseReceiving:__Arrived(1, group) + + -- Load the cargo in the warehouse. + Cargo:Load(warehouse.warehouse) + + -- Trigger Arrived event. + warehouse:Arrived(group) end --- On after BackHome event. @@ -3092,15 +3234,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Start dispatcher. CargoTransport:__Start(5) - - -- Add cargo groups to request. - Pending.transportgroupset=TransportSet - - -- Add request to pending queue. - table.insert(self.pending, Pending) - - -- Delete request from queue. - self:_DeleteQueueItem(Request, self.queue) end @@ -3466,7 +3599,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) text=text..string.format("Deploying all %d ground assets.", nground) -- Add self request. - self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, "all", nil, nil , 0) + self:AddRequest(self, WAREHOUSE.Descriptor.CATEGORY, Group.Category.GROUND, WAREHOUSE.Quantity.ALL, nil, nil , 0) else text=text..string.format("No ground assets currently available.") end @@ -3621,7 +3754,7 @@ function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) end ---- On after "Destroyed" event. Warehouse was destroyed. All services are stopped. +--- On after "Destroyed" event. Warehouse was destroyed. All services are stopped. Warehouse is going to "Stopped" state in one minute. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. @@ -3632,8 +3765,8 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) local text=string.format("Warehouse %s was destroyed!", self.alias) self:_InfoMessage(text) - -- Stop warehouse FSM. - self:Stop() + -- Stop warehouse FSM in one minute. + self:__Stop(60) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3658,10 +3791,12 @@ function WAREHOUSE:_RouteGround(group, request) -- Check if an off road path has been defined. local hasoffroad=self:HasConnectionOffRoad(request.warehouse, self.Debug) + -- Check if any off road paths have be defined. They have priority! if hasoffroad then - -- Get off road path to remote warehouse. - local path=self.offroadpaths[request.warehouse.warehouse:GetName()] + -- Get off road path to remote warehouse. If more have been defined, pick one randomly. + local remotename=request.warehouse.warehouse:GetName() + local path=self.offroadpaths[remotename][math.random(#self.offroadpaths[remotename])] -- Loop over user defined shipping lanes. for i=1,#path do @@ -3723,8 +3858,12 @@ function WAREHOUSE:_RouteNaval(group, request) local _speed=group:GetSpeedMax()*0.8 -- Get shipping lane to remote warehouse. - local lane=self.shippinglanes[request.warehouse.warehouse:GetName()] + --local lane=self.shippinglanes[request.warehouse.warehouse:GetName()] + -- Get off road path to remote warehouse. If more have been defined, pick one randomly. + local remotename=request.warehouse.warehouse:GetName() + local path=self.shippinglanes[remotename][math.random(#self.ship[remotename])] + if lane then -- Route waypoints. @@ -4022,6 +4161,23 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) 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)) + + 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)) + end + + end + + end end @@ -4242,7 +4398,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) -- Request from enemy coalition? if self.coalition~=request.warehouse.coalition then - self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coaltion! Own coalition %d. Requesting warehouse %d", self.coalition, request.warehouse.coalition)) + self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coaltion! Own coalition %d != %d of requesting warehouse.", self.coalition, request.warehouse.coalition)) valid=false end @@ -4505,9 +4661,15 @@ function WAREHOUSE:_CheckRequestNow(request) return false end - + + -- If no transport is requested, assets need to be mobile unless it is a self request. + local onlymobile=false + if request.ntransport==0 and not request.toself then + onlymobile=true + end + -- Check if number of requested assets is in stock. - local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) + local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset, onlymobile) -- Check if enough assets are in stock. if not _enough then @@ -4543,6 +4705,12 @@ function WAREHOUSE:_CheckRequestNow(request) request.cargoassets=_assets request.cargoattribute=_assetattribute request.cargocategory=_assetcategory + + env.info("FF selected cargo assets") + for _,_asset in pairs(_assets) do + local asset=_asset --#WAREHOUSE.Assetitem + env.info(string.format("- name = %s", asset.templatename)) + end end @@ -5117,25 +5285,33 @@ 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.Assetitem}. --- @param #string item Descriptor --- @param value Value of the descriptor. +-- @param #string descriptor Descriptor describing the filtered assets. +-- @param attribute Value of the descriptor. -- @param #number nmax (Optional) Maximum number of items that will be returned. Default nmax=nil is all matching items are returned. +-- @param #boolean mobile (Optional) If true, filter only mobile assets. -- @return #table Filtered stock items table. -- @return #number Total number of (requested) assets available. -- @return #boolean If true, enough assets are available. -function WAREHOUSE:_FilterStock(stock, item, value, nmax) +function WAREHOUSE:_FilterStock(stock, descriptor, attribute, nmax, mobile) -- Default all. - nmax=nmax or "all" + nmax=nmax or WAREHOUSE.Quantity.ALL + if mobile==nil then + mobile=false + end -- Filtered array. local filtered={} -- Count total number in stock. local ntot=0 - for _,_stock in ipairs(stock) do - if _stock[item]==value then - ntot=ntot+1 + for _,_asset in ipairs(stock) do + local asset=_asset --#WAREHOUSE.Assetitem + local ismobile=asset.speedmax>0 + if asset[descriptor]==attribute then + if (mobile==true and ismobile) or mobile==false then + ntot=ntot+1 + end end end @@ -5146,15 +5322,15 @@ function WAREHOUSE:_FilterStock(stock, item, value, nmax) -- Handle string input for nmax. if type(nmax)=="string" then - if nmax:lower()=="all" then + if nmax:lower()==WAREHOUSE.Quantity.ALL then nmax=ntot - elseif nmax:lower()=="threequarter" then + elseif nmax==WAREHOUSE.Quantity.THREEQUARTERS then nmax=ntot*3/4 - elseif nmax:lower()=="half" then + elseif nmax==WAREHOUSE.Quantity.HALF then nmax=ntot/2 - elseif nmax:lower()=="third" then + elseif nmax==WAREHOUSE.Quantity.THIRD then nmax=ntot/3 - elseif nmax:lower()=="quarter" then + elseif nmax==WAREHOUSE.Quantity.QUARTER then nmax=ntot/4 else nmax=math.min(1, ntot) @@ -5162,13 +5338,24 @@ function WAREHOUSE:_FilterStock(stock, item, value, nmax) end -- Loop over stock items. - for _i,_stock in ipairs(stock) do - if _stock[item]==value then - _stock.pos=_i - table.insert(filtered, _stock) - if nmax~=nil and #filtered>=nmax then - return filtered, ntot, true - end + for _i,_asset in ipairs(stock) do + local asset=_asset --#WAREHOUSE.Assetitem + + -- Check if asset has the right attribute. + if asset[descriptor]==attribute then + + -- Check if asset has to be mobile. + if (mobile and asset.speedmax>0) or (not mobile) then + + -- Add asset to filtered table. + table.insert(filtered, asset) + + -- Break loop if nmax was reached. + if nmax~=nil and #filtered>=nmax then + return filtered, ntot, true + end + + end end end @@ -5210,7 +5397,8 @@ function WAREHOUSE:_GetAttribute(group) local awacs=group:HasAttribute("AWACS") local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") local bomber=group:HasAttribute("Bombers") - local tanker=group:HasAttribute("Tankers") + local tanker=group:HasAttribute("Tankers") + local uav=group:HasAttribute("UAV") -- Helicopters local transporthelo=group:HasAttribute("Transport helicopters") local attackhelicopter=group:HasAttribute("Attack helicopters") @@ -5224,6 +5412,8 @@ function WAREHOUSE:_GetAttribute(group) local infantry=group:HasAttribute("Infantry") local artillery=group:HasAttribute("Artillery") local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") + local aaa=group:HasAttribute("AAA") + local sam=group:HasAttribute("SAM") -- Train local train=group:GetCategory()==Group.Category.TRAIN @@ -5252,6 +5442,8 @@ function WAREHOUSE:_GetAttribute(group) attribute=WAREHOUSE.Attribute.AIR_TRANSPORTHELO elseif attackhelicopter then attribute=WAREHOUSE.Attribute.AIR_ATTACKHELO + elseif uav then + attribute=WAREHOUSE.Attribute.AIR_UAV elseif apc then attribute=WAREHOUSE.Attribute.GROUND_APC elseif truck then @@ -5262,6 +5454,10 @@ function WAREHOUSE:_GetAttribute(group) attribute=WAREHOUSE.Attribute.GROUND_ARTILLERY elseif tank then attribute=WAREHOUSE.Attribute.GROUND_TANK + elseif aaa then + attribute=WAREHOUSE.Attribute.GROUND_AAA + elseif sam then + attribute=WAREHOUSE.Attribute.GROUND_SAM elseif train then attribute=WAREHOUSE.Attribute.GROUND_TRAIN elseif aircraftcarrier then @@ -5273,9 +5469,16 @@ function WAREHOUSE:_GetAttribute(group) elseif unarmedship then attribute=WAREHOUSE.Attribute.NAVAL_UNARMEDSHIP else - attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN + if group:IsGround() then + attribute=WAREHOUSE.Attribute.GROUND_OTHER + elseif group:IsShip() then + attribute=WAREHOUSE.Attribute.NAVAL_OTHER + elseif group:IsAir() then + attribute=WAREHOUSE.Attribute.AIR_OTHER + else + attribute=WAREHOUSE.Attribute.OTHER_UNKNOWN + end end - end return attribute @@ -5299,7 +5502,7 @@ 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. +-- @return #table Data table holding the numbers, i.e. data[attibute]=n. function WAREHOUSE:GetStockInfo(stock) local _data={} @@ -5389,6 +5592,7 @@ function WAREHOUSE:_PrintQueue(queue, name) if qitem.timestamp then clock=tostring(UTILS.SecondsToClock(qitem.timestamp)) end + local assignment=tostring(qitem.assignment) local requestor=qitem.warehouse.alias local requestorAirbaseCat=qitem.category local assetdesc=qitem.assetdesc @@ -5418,8 +5622,8 @@ function WAREHOUSE:_PrintQueue(queue, name) -- Output text: text=text..string.format( - "\n%d) UID=%d, Prio=%d, Clock=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%d / #alive=%s / #home=%s", - i, uid, prio, clock, requestor, airbasename, requestorAirbaseCat, assetdesc, assetdescval, nasset, ncargogroupset, ndelivered, transporttype, ntransport, ntransportalive, ntransporthome) + "\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%d / #alive=%s / #home=%s", + i, uid, prio, clock, assignment, requestor, airbasename, requestorAirbaseCat, assetdesc, assetdescval, nasset, ncargogroupset, ndelivered, transporttype, ntransport, ntransportalive, ntransporthome) end @@ -5471,9 +5675,11 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) local text="Stock:\n" local total=0 for _attribute,_count in pairs(_data) do - local attribute=tostring(UTILS.Split(_attribute, "_")[2]) - text=text..string.format("%s = %d\n", attribute,_count) - total=total+_count + if _count>0 then + local attribute=tostring(UTILS.Split(_attribute, "_")[2]) + text=text..string.format("%s = %d\n", attribute,_count) + total=total+_count + end end text=text..string.format("===================\n") text=text..string.format("Total = %d\n", total) @@ -5485,7 +5691,7 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) return text end ---- Create or update mark text at warehouse, which is displayed in F10 map. +--- Create or update mark text at warehouse, which is displayed in F10 map showing how many assets of each type are in stock. -- Only the coaliton of the warehouse owner is able to see it. -- @param #WAREHOUSE self -- @return #string Text about warehouse stock @@ -5500,12 +5706,13 @@ function WAREHOUSE:_UpdateWarehouseMarkText() local _data=self:GetStockInfo(self.stock) -- Text. - local text="Warehouse Stock:\n" - text=text..string.format("Total assets: %d\n", #_data) - local total=0 + local text=string.format("Warehouse Stock - total assets %d:\n", #self.stock) + for _attribute,_count in pairs(_data) do - local attribute=tostring(UTILS.Split(_attribute, "_")[2]) - text=text..string.format("%s=%d, ", attribute,_count) + if _count>0 then + local attribute=tostring(UTILS.Split(_attribute, "_")[2]) + text=text..string.format("%s=%d, ", attribute,_count) + end end -- Create/update marker at warehouse in F10 map. From bd1aec6deba0df12707efade9a1be6529535a1be Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 9 Sep 2018 13:46:12 +0200 Subject: [PATCH 53/73] Warehouse v0.3.9 Fixes and minor adjustments. --- .../Moose/Functional/Warehouse.lua | 64 +++++++++++-------- 1 file changed, 38 insertions(+), 26 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index dc321d0d7..7bf346cf7 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -987,22 +987,23 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.8" +WAREHOUSE.version="0.3.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. +-- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Test capturing a neutral warehouse. --- TODO: Make more examples: ARTY, CAP, --- TODO: Add SAMs and UAVs to generalized attributes. +-- TODO: Make more examples: ARTY, CAP, ... -- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. -- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. --- TODO: Added habours as interface for transport to from warehouses? --- TODO: Write documentation. -- TODO: Handle the case when units of a group die during the transfer. +-- TODO: Added habours as interface for transport to from warehouses? -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua! +-- DONE: Write documentation. +-- DONE: Add AAA, SAMs and UAVs to generalized attributes. -- DONE: Add warehouse quantity enumerator. -- DONE: Test mortars. Immobile units need a transport. -- DONE: Set ROE for spawned groups. @@ -1095,7 +1096,7 @@ function WAREHOUSE:New(warehouse, alias) -- From State --> Event --> To State self:AddTransition("NotReadyYet", "Load", "Loaded") -- TODO Load the warehouse state. No sure if it should be in stopped state. self:AddTransition("NotReadyYet", "Start", "Running") -- Start the warehouse from scratch. - self:AddTransition("Loaded", "Start", "Running") -- Start the warehouse when loaded from disk. + self:AddTransition("Loaded", "Start", "Running") -- TODO Start the warehouse when loaded from disk. self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("*", "AddAsset", "*") -- Add asset to warehouse stock. self:AddTransition("*", "AddRequest", "*") -- New request from other warehouse. @@ -1140,7 +1141,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. --- Triggers the FSM event "Pause". Pauses the warehouse. Assets can still be added and requests be made. However, requests are not processed. - -- @function [parent=#WAREHOUSE] Pauses + -- @function [parent=#WAREHOUSE] Pause -- @param #WAREHOUSE self --- Triggers the FSM event "Pause" after a delay. Pauses the warehouse. Assets can still be added and requests be made. However, requests are not processed. @@ -2065,7 +2066,7 @@ function WAREHOUSE:onafterStatus(From, Event, To) end -- Call status again in ~30 sec (user choice). - self:__Status(self.dTstatus) + self:__Status(-self.dTstatus) end --- Count alive and filter dead groups. @@ -2088,7 +2089,10 @@ function WAREHOUSE:_FilterDead(groupset) end end - self:E(string.format("FF FilterDead: Alive=%d, Dead=%d", nalive, #dead)) + -- 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 @@ -2294,6 +2298,8 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu end + -- Update status. + self:__Status(-1) end --- Find an asset in the the global warehouse db. @@ -2788,11 +2794,6 @@ function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor nTransport=1 end end - - -- Not more transports than assets. - --if type(nAsset)=="number" then - -- nTransport=math.min(nAsset, nTransport) - --end -- Self request? local toself=false @@ -2824,7 +2825,13 @@ function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor -- Add request to queue. table.insert(self.queue, request) + + local text=string.format("Warehouse %s: New request from %s. Descriptor %s=%s, #assets=%s; Transport=%s, #transports =%s.", + self.alias, warehouse.alias, request.assetdesc, tostring(request.assetdescval), tostring(request.nasset), request.transporttype, tostring(request.ntransport)) + self:_DebugMessage(text, 5) + -- Update status + self:__Status(-1) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4650,18 +4657,14 @@ end -- @param #WAREHOUSE.Queueitem request The request to be checked. -- @return #boolean If true, request can be executed. If false, something is not right. function WAREHOUSE:_CheckRequestNow(request) - - -- Assume request is okay and check scenarios. - local okay=true - + -- Check if receiving warehouse is running. We do allow self requests if the warehouse is under attack though! - if (not request.warehouse:IsRunning()) and (not request.toself and self:IsAttacked()) then + if (request.warehouse:IsRunning()==false) and not (request.toself and self:IsAttacked()) then local text=string.format("Warehouse %s: Request denied! Receiving warehouse %s is not running. Current state %s.", self.alias, request.warehouse.alias, request.warehouse:GetState()) self:_InfoMessage(text, 5) return false end - -- If no transport is requested, assets need to be mobile unless it is a self request. local onlymobile=false if request.ntransport==0 and not request.toself then @@ -4706,11 +4709,13 @@ function WAREHOUSE:_CheckRequestNow(request) request.cargoattribute=_assetattribute request.cargocategory=_assetcategory - env.info("FF selected cargo assets") - for _,_asset in pairs(_assets) do + -- Debug info: + local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n", request.cargoattribute, request.cargocategory) + for _i,_asset in pairs(_assets) do local asset=_asset --#WAREHOUSE.Assetitem - env.info(string.format("- name = %s", asset.templatename)) + text=text..string.format("%d) asset name=%s, type=%s, category=%d, #units=%d",_i, asset.templatename, asset.unittype, asset.category, asset.nunits) end + self:T(self.wid..text) end @@ -4742,6 +4747,14 @@ function WAREHOUSE:_CheckRequestNow(request) request.transportassets=_transports request.transportattribute=_transportattribute request.transportcategory=_transportcategory + + -- Debug info: + local text=string.format("Selected transport assets, attibute=%s, category=%d:\n", request.transportattribute, request.transportcategory) + for _i,_asset in pairs(_assets) do + local asset=_asset --#WAREHOUSE.Assetitem + text=text..string.format("%d) asset name=%s, type=%s, category=%d, #units=%d",_i, asset.templatename, asset.unittype, asset.category, asset.nunits) + end + self:T(self.wid..text) else @@ -5641,9 +5654,9 @@ function WAREHOUSE:_DisplayStatus() end local text=string.format("\n------------------------------------------------------\n") - text=text..string.format("Warehouse %s status:\n", self.alias) + 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", self:GetState()) + --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) @@ -5652,7 +5665,6 @@ function WAREHOUSE:_DisplayStatus() text=text..string.format("------------------------------------------------------\n") text=text..self:_GetStockAssetsText() self:T(text) - --TODO: number of ground, air, naval assets. end --- Get text about warehouse stock. From 53ac9ca5006d3980090dc385a93e934ae9cd1273 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 9 Sep 2018 22:04:31 +0200 Subject: [PATCH 54/73] Warehouse v0.4.0 Added some onevent functions for AI_CARGO Added start command for CONTROLLER --- .../Moose/Functional/Warehouse.lua | 31 +++++++++++++++++-- .../Moose/Wrapper/Controllable.lua | 8 +++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 7bf346cf7..dbb2c19a4 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -987,7 +987,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.3.9" +WAREHOUSE.version="0.4.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -3189,8 +3189,32 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:E(self.wid.."ERROR: Unknown transporttype!") end + -------------------------------- + -- 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) + end + + function CargoTransport:OnAfterDeployed(From,Event,To,CarrierGroup,DeployZone) + local text=string.format("FF Carrier group %s deployed event for deploy zone %s.", CarrierGroup:GetName(), DeployZone:GetName()) + env.info(text) + end + + function CargoTransport:OnAfterHome(From,Event,To,CarrierGroup,Coordinate,Speed,HomeZone) + local text=string.format("FF Carrier group %s going home to zone %s.", CarrierGroup:GetName(), HomeZone:GetName()) + env.info(text) + end + --- Function called when cargo has arrived and was unloaded. - function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo) + function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone) self:I("FF OnAfterUnloaded:") self:I({From=From}) @@ -3923,7 +3947,8 @@ function WAREHOUSE:_RouteAir(aircraft) self:T2(self.wid..string.format("RouteAir aircraft group %s alive=%s", aircraft:GetName(), tostring(aircraft:IsAlive()))) -- Give start command to activate uncontrolled aircraft. - aircraft:SetCommand({id='Start', params={}}) + --aircraft:SetCommand({id='Start', params={}}) + aircraft:StartUncontrolled() -- Debug info. self:T2(self.wid..string.format("RouteAir aircraft group %s alive=%s (after start command)", aircraft:GetName(), tostring(aircraft:IsAlive()))) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 97529a6fa..92aed3843 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -611,6 +611,14 @@ function CONTROLLABLE:CommandStopRoute( StopRoute ) end +--- Give an uncontrolled air controllable the start command. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:StartUncontrolled() + self:SetCommand({id='Start', params={}}) + return self +end + -- TASKS FOR AIR CONTROLLABLES From cc9b695b68185f54e8b1ee64b343cbbf09165af0 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 9 Sep 2018 23:57:17 +0200 Subject: [PATCH 55/73] Warehouse v0.4.1 Improved JobDone function. Fixed bugs. --- .../Moose/Functional/Warehouse.lua | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index dbb2c19a4..e9cb77b8c 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -987,7 +987,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.0" +WAREHOUSE.version="0.4.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -2114,10 +2114,18 @@ function WAREHOUSE:_JobDone() local request=request --#WAREHOUSE.Pendingitem -- Count number of cargo groups still alive and filter out dead groups. - local ncargo=self:_FilterDead(request.cargogroupset) + 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. - local ntransport=self:_FilterDead(request.transportgroupset) + local ntransport=0 + if request.transportgroupset then + request.transportgroupset:Count() + end + --self:_FilterDead(request.transportgroupset) if ncargo==0 and ntransport==0 then @@ -2293,7 +2301,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Destroy group if it is alive. if group:IsAlive()==true then self:_DebugMessage(string.format("Destroying group %s.", group:GetName()), 5) - group:Destroy() + group:Destroy(true) end end @@ -3010,7 +3018,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then _loadradius=10000 elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - _loadradius=1000 + --_loadradius=1000 + _loadradius=nil elseif Request.transporttype==WAREHOUSE.TransportType.APC then _loadradius=1000 end @@ -3232,9 +3241,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) local text=string.format("FF Group %s was unloaded from carrier %s.", tostring(group:GetName()), tostring(Carrier:GetName())) env.info(text) - -- Load the cargo in the warehouse. - Cargo:Load(warehouse.warehouse) + --Cargo:Load(warehouse.warehouse) -- Trigger Arrived event. warehouse:Arrived(group) @@ -3501,7 +3509,7 @@ function WAREHOUSE:_UpdatePending(group) if caid==aid then request.transportgroupset:Remove(transportgroup:GetName()) request.ntransporthome=request.ntransporthome+1 - env.info("FF transport back home # "..request.ntransporthome) + env.info(string.format("Transport back home #%s", request.ntransporthome)) isCargo=false break end @@ -3520,7 +3528,7 @@ function WAREHOUSE:_UpdatePending(group) if caid==aid then request.cargogroupset:Remove(cargogroup:GetName()) request.ndelivered=request.ndelivered+1 - env.info("FF delivered cargo # "..request.ndelivered) + env.info(string.format("FF delivered cargo # ", request.ndelivered)) isCargo=true break end @@ -3856,12 +3864,9 @@ function WAREHOUSE:_RouteGround(group, request) table.insert(Waypoints, #Waypoints+1, ToWP) end - - -- Task function triggering the arrived event. - --local TaskFunction = group:TaskFunction("WAREHOUSE._Arrived", self) -- Task function triggering the arrived event at the last waypoint. - local TaskFunction = self:_SimpleTaskFunction("warehouse:_ArrivedSimple", group) + local TaskFunction = self:_SimpleTaskFunction("warehouse:_Arrived", group) -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] @@ -3893,7 +3898,7 @@ function WAREHOUSE:_RouteNaval(group, request) -- Get off road path to remote warehouse. If more have been defined, pick one randomly. local remotename=request.warehouse.warehouse:GetName() - local path=self.shippinglanes[remotename][math.random(#self.ship[remotename])] + local lane=self.shippinglanes[remotename][math.random(#self.ship[remotename])] if lane then @@ -3914,7 +3919,7 @@ function WAREHOUSE:_RouteNaval(group, request) end -- Task function triggering the arrived event at the last waypoint. - local TaskFunction = self:_SimpleTaskFunction("warehouse:_ArrivedSimple", group) + local TaskFunction = self:_SimpleTaskFunction("warehouse:_Arrived", group) -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] @@ -3976,8 +3981,8 @@ function WAREHOUSE:_RouteTrain(Group, Coordinate, Speed) -- Create a local Waypoints = Group:TaskGroundOnRailRoads(Coordinate, Speed) - -- Task function triggering the arrived event. - local TaskFunction = Group:TaskFunction("WAREHOUSE._Arrived", self) + -- Task function triggering the arrived event at the last waypoint. + local TaskFunction = self:_SimpleTaskFunction("warehouse:_Arrived", Group) -- Put task function on last waypoint. local Waypoint = Waypoints[#Waypoints] @@ -3989,21 +3994,10 @@ function WAREHOUSE:_RouteTrain(Group, Coordinate, Speed) end --- Task function for last waypoint. Triggering the "Arrived" event. --- @param Wrapper.Group#GROUP group The group that arrived. --- @param #WAREHOUSE warehouse Warehouse self. -function WAREHOUSE._Arrived(group, warehouse) - env.info(warehouse.wid..string.format("Group %s arrived at destination.", tostring(group:GetName()))) - - --Trigger "Arrived" event. - warehouse:__Arrived(1, group) - -end - ---- Simple task function for last waypoint. Triggering the "Arrived" event. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that arrived. -function WAREHOUSE:_ArrivedSimple(group) - self:_DebugMessage(string.format("Group %s arrived (simple)!", tostring(group:GetName()))) +function WAREHOUSE:_Arrived(group) + self:_DebugMessage(string.format("Group %s arrived!", tostring(group:GetName()))) if group then --Trigger "Arrived event. @@ -4143,7 +4137,6 @@ function WAREHOUSE:_OnEventLanding(EventData) -- Group arrived. self:Arrived(group) - --self:__AddAsset(30, group) end end From 85505f3febbbb7ebf1ea183de9713317723ab942 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 10 Sep 2018 16:32:10 +0200 Subject: [PATCH 56/73] Warehouse v0.4.1w --- .../Moose/Functional/Warehouse.lua | 264 +++++++++++++----- 1 file changed, 193 insertions(+), 71 deletions(-) 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) From 552718e3039b0a060a2f058315578ce9581d4ac6 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 11 Sep 2018 00:04:33 +0200 Subject: [PATCH 57/73] Warehouse v0.4.1alpha Not working version. fixed a few bugs. Uncommented check for coordinate in CargoGroup.lua etc. --- Moose Development/Moose/Cargo/CargoGroup.lua | 11 +- .../Moose/Functional/Warehouse.lua | 540 +++++++++--------- 2 files changed, 267 insertions(+), 284 deletions(-) diff --git a/Moose Development/Moose/Cargo/CargoGroup.lua b/Moose Development/Moose/Cargo/CargoGroup.lua index 9bb3a8c1b..48378297a 100644 --- a/Moose Development/Moose/Cargo/CargoGroup.lua +++ b/Moose Development/Moose/Cargo/CargoGroup.lua @@ -661,12 +661,13 @@ do -- CARGO_GROUP else CargoCoordinate = Cargo.CargoObject:GetCoordinate() end - --- if CargoCoordinate then + + -- FF check if coordinate could be obtained. This was commented out for some (unknown) reason. But the check seems valid! + if CargoCoordinate then Distance = Coordinate:Get2DDistance( CargoCoordinate ) --- else --- return false --- end + else + return false + end self:F( { Distance = Distance, LoadRadius = self.LoadRadius } ) if Distance <= self.LoadRadius then diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 3af2025c4..abaa4d385 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -991,7 +991,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.1w" +WAREHOUSE.version="0.4.1alpha" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -2082,7 +2082,7 @@ function WAREHOUSE:_JobDone() local done={} for _,request in pairs(self.pending) do local request=request --#WAREHOUSE.Pendingitem - + -- Count number of cargo groups. local ncargo=0 if request.cargogroupset then @@ -2092,7 +2092,7 @@ function WAREHOUSE:_JobDone() -- Count number of transport groups (if any). local ntransport=0 if request.transportgroupset then - request.transportgroupset:Count() + ntransport=request.transportgroupset:Count() end if ncargo==0 and ntransport==0 then @@ -2114,13 +2114,20 @@ function WAREHOUSE:_JobDone() self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!", self.alias, request.uid, ncargo)) -- All transports are dead but there is still cargo left ==> Put cargo back into stock. - for _,group in pairs(request.cargogroupset) do + for _,group in pairs(request.cargogroupset:GetSetObjects()) do local group=group --Wrapper.Group#GROUP - group:SmokeRed() - -- Add all assets back to stock - if group:IsPartlyOrCompletelyInZone(self.spawnzone) then - self:__AddAsset(5, group) + -- Check if group is alive. + if group and group:IsAlive() then + + if self.Debug then + group:SmokeRed() + end + + -- Add all assets back to stock + if group:IsPartlyOrCompletelyInZone(self.spawnzone) then + self:__AddAsset(5, group) + end end end @@ -2142,13 +2149,21 @@ function WAREHOUSE:_JobDone() self:_InfoMessage(string.format("Warehouse %s: No cargo assets left for request id=%s. Putting remaining %s transport assets back into warehouse!", self.alias, request.uid, ntransport)) -- All transports are dead but there is still cargo left ==> Put cargo back into stock. - for _,group in pairs(request.transportgroupset) do + for _,group in pairs(request.transportgroupset:GetSetObjects()) do local group=group --Wrapper.Group#GROUP - group:SmokeRed() - -- Add all assets back to stock - if group:IsPartlyOrCompletelyInZone(self.spawnzone) then - self:__AddAsset(5, group) + -- Check if group is alive. + if group and group:IsAlive() then + + -- Assets back to stock. + if group:IsPartlyOrCompletelyInZone(self.spawnzone) then + + if self.Debug then + group:SmokeRed() + end + + self:__AddAsset(5, group) + end end end @@ -2192,43 +2207,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- This is a known asset -- --------------------------- - -- Get the warehouse this group belonged to. (could also be the same for self requests). - local warehouseOld=self:_FindWarehouseInDB(wid) - - -- Now get the request from the pending queue and update it. - - -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. - local request, isCargo=warehouseOld:_UpdatePending(group) - - if request then - - -- Number of cargo assets still in group set. - if isCargo==true then - - -- Current size of cargo group set. - local ncargo=request.cargogroupset:Count() - - -- Debug message. - local text=string.format("Cargo %d of %s added to warehouse %s stock. Assets still to deliver %d.", - request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) - self:_DebugMessage(text, 5) - - -- All cargo delivered. - if ncargo==0 then - warehouseOld:Delivered(request) - end - - elseif isCargo==false then - - -- Current size of cargo group set. - local ntransport=request.transportgroupset:Count() - - -- Debug message. - local text=string.format("Transport %d of %s added to warehouse %s stock. Transports still missing %d.", - request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) - self:_DebugMessage(text, 5) - - end + if true then -- Get the asset from the global DB. local asset=self:_FindAssetInDB(group) @@ -2277,6 +2256,73 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu self:__Status(-1) end + +--- Update the pending requests by removing assets that have arrived. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group that has arrived at its destination. +-- @return #WAREHOUSE.Pendingitem The updated request from the pending queue. +-- @return #boolean If true, group is a cargo asset. If false, group is a transport asset. If nil, group is neither cargo nor transport. +function WAREHOUSE:_UpdatePending(group) + + -- Get request from group name. + local request=self:_GetRequestOfGroup(group, self.pending) + + -- Get the IDs for this group. In particular, we use the asset ID to figure out which group was delivered. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + local isCargo=nil + + if request then + + -- If this request was already delivered. + if self.delivered[rid]==true then + + -- Loop over transport groups. + for _,_transportgroup in pairs(request.transportgroupset:GetSetObjects()) do + local transportgroup=_transportgroup --Wrapper.Group#GROUP + + -- IDs of cargo group. + local cwid,caid,crid=self:_GetIDsFromGroup(transportgroup) + + -- Remove group from transport group set and increase home counter. + if caid==aid then + request.transportgroupset:Remove(transportgroup:GetName()) + request.ntransporthome=request.ntransporthome+1 + env.info(string.format("Transport back home #%s", request.ntransporthome)) + isCargo=false + break + end + end + + else + + -- Loop over cargo groups. + for _,_cargogroup in pairs(request.cargogroupset:GetSetObjects()) do + local cargogroup=_cargogroup --Wrapper.Group#GROUP + + -- IDs of cargo group. + local cwid,caid,crid=self:_GetIDsFromGroup(cargogroup) + + -- Remove group from cargo group set and increase delivered counter. + if caid==aid then + request.cargogroupset:Remove(cargogroup:GetName()) + request.ndelivered=request.ndelivered+1 + env.info(string.format("FF delivered cargo # ", request.ndelivered)) + isCargo=true + break + end + end + + end + else + self:E(self.wid..string.format("WARNING: pending request could not be updated since request did not exist in pending queue!")) + end + + return request, isCargo +end + + + --- Find an asset in the the global warehouse db. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which it is assumed that it has a registered asset. @@ -3105,7 +3151,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- 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 _,carriergroup in pairs(TransportSet:GetSetObjects()) do for _,carrierunit in pairs(carriergroup:GetUnits()) do Pending.carriercargo[carrierunit:GetName()]={} end @@ -3190,14 +3236,14 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) env.info(text) end - function CargoTransport:OnAfterHome(From,Event,To,CarrierGroup,Coordinate,Speed,HomeZone) + function CargoTransport:OnAfterHome(From, Event, To, CarrierGroup, Coordinate, Speed, HomeZone) local text=string.format("FF Carrier group %s going home to zone %s.", CarrierGroup:GetName(), HomeZone:GetName()) 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()) + local text=string.format("FF Carrier group %s loaded cargo %s into unit %s in pickup zone %s", Carrier:GetName(), Cargo:GetObject():GetName(), CarrierUnit:GetName(), PickupZone:GetName()) env.info(text) -- Get group object. @@ -3473,69 +3519,7 @@ function WAREHOUSE:_GetAssetFromGroupRequest(group,request) local asset=request.assets[aid] end ---- Update the pending requests by removing assets that have arrived. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group that has arrived at its destination. --- @return #WAREHOUSE.Pendingitem The updated request from the pending queue. --- @return #boolean If true, group is a cargo asset. If false, group is a transport asset. If nil, group is neither cargo nor transport. -function WAREHOUSE:_UpdatePending(group) - - -- Get request from group name. - local request=self:_GetRequestOfGroup(group, self.pending) - - -- Get the IDs for this group. In particular, we use the asset ID to figure out which group was delivered. - local wid,aid,rid=self:_GetIDsFromGroup(group) - - local isCargo=nil - - if request then - - -- If this request was already delivered. - if self.delivered[rid]==true then - - -- Loop over transport groups. - for _,_transportgroup in pairs(request.transportgroupset:GetSetObjects()) do - local transportgroup=_transportgroup --Wrapper.Group#GROUP - - -- IDs of cargo group. - local cwid,caid,crid=self:_GetIDsFromGroup(transportgroup) - - -- Remove group from transport group set and increase home counter. - if caid==aid then - request.transportgroupset:Remove(transportgroup:GetName()) - request.ntransporthome=request.ntransporthome+1 - env.info(string.format("Transport back home #%s", request.ntransporthome)) - isCargo=false - break - end - end - - else - - -- Loop over cargo groups. - for _,_cargogroup in pairs(request.cargogroupset:GetSetObjects()) do - local cargogroup=_cargogroup --Wrapper.Group#GROUP - - -- IDs of cargo group. - local cwid,caid,crid=self:_GetIDsFromGroup(cargogroup) - - -- Remove group from cargo group set and increase delivered counter. - if caid==aid then - request.cargogroupset:Remove(cargogroup:GetName()) - request.ndelivered=request.ndelivered+1 - env.info(string.format("FF delivered cargo # ", request.ndelivered)) - isCargo=true - break - end - end - - end - else - self:E(self.wid..string.format("WARNING: pending request could not be updated since request did not exist in pending queue!")) - end - - return request, isCargo -end + --- On after "Delivered" event. Triggered when all asset groups have reached their destination. Corresponding request is deleted from the pending queue. @@ -4005,6 +3989,122 @@ end -- Event handler functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Warehouse event function, handling the birth of a unit. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA EventData Event data. +function WAREHOUSE:_OnEventBirth(EventData) + self:T3(self.wid..string.format("Warehouse %s (id=%s) captured event birth!", self.alias, self.uid)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + -- Note: Remember, group:IsAlive might(?) not return true here. + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:T(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s.", self.alias, EventData.IniUnitName)) + else + --self:T3({wid=wid, uid=self.uid, match=(wid==self.uid), tw=type(wid), tu=type(self.uid)}) + end + end +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function handling the event when a (warehouse) unit starts its engines. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA EventData Event data. +function WAREHOUSE:_OnEventEngineStartup(EventData) + self:T3(self.wid..string.format("Warehouse %s captured event engine startup!",self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:T(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function handling the event when a (warehouse) unit takes off. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA EventData Event data. +function WAREHOUSE:_OnEventTakeOff(EventData) + self:T3(self.wid..string.format("Warehouse %s captured event takeoff!",self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:T(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function handling the event when a (warehouse) unit lands. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA EventData Event data. +function WAREHOUSE:_OnEventLanding(EventData) + self:T3(self.wid..string.format("Warehouse %s captured event landing!", self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + + -- Try to get UIDs from group name. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + -- Check that this group belongs to this warehouse. + if wid~=nil and wid==self.uid then + + -- Debug info. + self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) + + -- Check if all cargo was delivered. + if self.delivered[rid]==true then + + -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. + if group:GetCategory()==Group.Category.HELICOPTER then + if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then + + -- Debug message. + self:_DebugMessage("Helicopter landed in spawn zone. No pending request. Putting back into stock.") + if self.Debug then + group:SmokeWhite() + end + + -- Group arrived. + self:Arrived(group) + + end + end + + end + + end + end +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Function handling the event when a (warehouse) unit shuts down its engines. +-- @param #WAREHOUSE self +-- @param Core.Event#EVENTDATA EventData Event data. +function WAREHOUSE:_OnEventEngineShutdown(EventData) + self:T3(self.wid..string.format("Warehouse %s captured event engine shutdown!", self.alias)) + + if EventData and EventData.IniGroup then + local group=EventData.IniGroup + local wid,aid,rid=self:_GetIDsFromGroup(group) + if wid==self.uid then + self:T(self.wid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.", self.alias, EventData.IniUnitName)) + end + end +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Arrived event if an air unit/group arrived at its destination. This can be an engine shutdown or a landing event. -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data table. @@ -4051,111 +4151,7 @@ function WAREHOUSE:_OnEventArrived(EventData) end ---- Warehouse event handling function. --- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA EventData Event data. -function WAREHOUSE:_OnEventBirth(EventData) - self:T3(self.wid..string.format("Warehouse %s (id=%s) captured event birth!", self.alias, self.uid)) - - if EventData and EventData.IniGroup then - local group=EventData.IniGroup - -- Note: Remember, group:IsAlive might(?) not return true here. - local wid,aid,rid=self:_GetIDsFromGroup(group) - if wid==self.uid then - self:T(self.wid..string.format("Warehouse %s captured event birth of its asset unit %s.", self.alias, EventData.IniUnitName)) - else - --self:T3({wid=wid, uid=self.uid, match=(wid==self.uid), tw=type(wid), tu=type(self.uid)}) - end - end -end - ---- Warehouse event handling function. --- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA EventData Event data. -function WAREHOUSE:_OnEventEngineStartup(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event engine startup!",self.alias)) - - if EventData and EventData.IniGroup then - local group=EventData.IniGroup - local wid,aid,rid=self:_GetIDsFromGroup(group) - if wid==self.uid then - self:T(self.wid..string.format("Warehouse %s captured event engine startup of its asset unit %s.", self.alias, EventData.IniUnitName)) - end - end -end - ---- Warehouse event handling function. --- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA EventData Event data. -function WAREHOUSE:_OnEventTakeOff(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event takeoff!",self.alias)) - - if EventData and EventData.IniGroup then - local group=EventData.IniGroup - local wid,aid,rid=self:_GetIDsFromGroup(group) - if wid==self.uid then - self:T(self.wid..string.format("Warehouse %s captured event takeoff of its asset unit %s.", self.alias, EventData.IniUnitName)) - end - end -end - ---- Warehouse event handling function. --- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA EventData Event data. -function WAREHOUSE:_OnEventLanding(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event landing!", self.alias)) - - if EventData and EventData.IniGroup then - local group=EventData.IniGroup - - -- Try to get UIDs from group name. - local wid,aid,rid=self:_GetIDsFromGroup(group) - - -- Check that this group belongs to this warehouse. - if wid~=nil and wid==self.uid then - - -- Debug info. - self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) - - -- Check if all cargo was delivered. - if self.delivered[rid]==true then - - -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. - if group:GetCategory()==Group.Category.HELICOPTER then - if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then - - -- Debug message. - self:_DebugMessage("Helicopter landed in spawn zone. No pending request. Putting back into stock.") - if self.Debug then - group:SmokeWhite() - end - - -- Group arrived. - self:Arrived(group) - - end - end - - end - - end - end -end - ---- Warehouse event handling function. --- @param #WAREHOUSE self --- @param Core.Event#EVENTDATA EventData Event data. -function WAREHOUSE:_OnEventEngineShutdown(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event engine shutdown!", self.alias)) - - if EventData and EventData.IniGroup then - local group=EventData.IniGroup - local wid,aid,rid=self:_GetIDsFromGroup(group) - if wid==self.uid then - self:T(self.wid..string.format("Warehouse %s captured event engine shutdown of its asset unit %s.", self.alias, EventData.IniUnitName)) - end - end -end +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Warehouse event handling function. -- @param #WAREHOUSE self @@ -4163,7 +4159,7 @@ end function WAREHOUSE:_OnEventCrashOrDead(EventData) self:T3(self.wid..string.format("Warehouse %s captured event dead or crash!",self.alias)) - if EventData and EventData.IniUnit then + if EventData and EventData.IniUnit and EventData.IniGroup then -- Check if warehouse was destroyed. local warehousename=self.warehouse:GetName() @@ -4173,40 +4169,62 @@ function WAREHOUSE:_OnEventCrashOrDead(EventData) -- Trigger Destroyed event. self:Destroyed() end - end - - -- Check if an asset unit was destroyed. - if EventData and EventData.IniGroup then - + + -- Check if an asset unit was destroyed. local group=EventData.IniGroup -- Get warehouse, asset and request IDs from the group name. local wid,aid,rid=self:_GetIDsFromGroup(group) + -- Check that we have the right warehouse. if wid==self.uid then + + -- Debug message. 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 + -- This is the right request. if request.uid==rid then -- 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) + + -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. + self:_UpdatePending(group) + + -- Number of cargo assets still in group set. + if isCargo==true then + + -- Current size of cargo group set. + local ncargo=request.cargogroupset:Count() + + -- Debug message. + local text=string.format("Cargo %d of %s added to warehouse %s stock. Assets still to deliver %d.", + request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) + self:_DebugMessage(text, 5) + + -- All cargo delivered. + if ncargo==0 then + self:Delivered(request) + end + + elseif isCargo==false then + + -- Current size of cargo group set. + local ntransport=request.transportgroupset:Count() + + -- Debug message. + local text=string.format("Transport %d of %s added to warehouse %s stock. Transports still missing %d.", + request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) + self:_DebugMessage(text, 5) + end - ]] + end - + end end end @@ -4246,7 +4264,7 @@ function WAREHOUSE:_UnitDead(deadunit, request) -- Remove dead group from carg group set. if isgroupdead==true then - request.cargogroupset:Remove(group, NoTriggerEvent) + request.cargogroupset:Remove(group:GetName(), NoTriggerEvent) end else @@ -4265,18 +4283,15 @@ function WAREHOUSE:_UnitDead(deadunit, request) 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) + for _,cargogroup in pairs(carrierunit) do + request.cargogroupset:Remove(cargogroup:GetName(),NoTriggerEvent) end end -- Whole carrier group is dead. Remove it from the carrier group set. if isgroupdead then - request.transportcargoset:Remove(group, NoTriggerEvent) + request.transportcargoset:Remove(group:GetName(), NoTriggerEvent) end else @@ -4285,7 +4300,7 @@ function WAREHOUSE:_UnitDead(deadunit, request) -- Remove dead group from cargo group set. if isgroupdead==true then - request.cargogroupset:Remove(group, NoTriggerEvent) + request.cargogroupset:Remove(group:GetName(), NoTriggerEvent) -- TODO: This as well? --request.transportcargoset:RemoveCargosByName(RemoveCargoNames) @@ -4295,40 +4310,7 @@ function WAREHOUSE:_UnitDead(deadunit, request) 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. From b7644efea5cec880a34a0b56a2f864770901765d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 11 Sep 2018 23:51:18 +0200 Subject: [PATCH 58/73] Warehouse v0.4.2 Improved destroyed group handling. Many other fixes. --- .../Moose/Functional/Warehouse.lua | 1487 +++++++++-------- 1 file changed, 822 insertions(+), 665 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index abaa4d385..8aa6dbb82 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -991,13 +991,13 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.1alpha" +WAREHOUSE.version="0.4.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. +-- DONE: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Test capturing a neutral warehouse. -- TODO: Make more examples: ARTY, CAP, ... @@ -1111,16 +1111,16 @@ 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") -- 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("Running", "Pause", "Paused") -- Pause the processing of new requests. Still possible to add assets and requests. + self:AddTransition("Paused", "Unpause", "Running") -- Unpause the warehouse. Queued requests are processed again. + self:AddTransition("*", "Stop", "Stopped") -- 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. - self:AddTransition("Attacked", "Defeated", "Running") -- DONE Attack by other coalition was defeated! - self:AddTransition("Attacked", "Captured", "Running") -- DONE Warehouse was captured by another coalition. It must have been attacked first. - self:AddTransition("*", "AirbaseCaptured", "*") -- DONE Airbase was captured by other coalition. - self:AddTransition("*", "AirbaseRecaptured", "*") -- DONE Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "Destoyed") -- DONE Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("*", "Attacked", "Attacked") -- Warehouse is under attack by enemy coalition. + self:AddTransition("Attacked", "Defeated", "Running") -- Attack by other coalition was defeated! + self:AddTransition("Attacked", "Captured", "Running") -- Warehouse was captured by another coalition. It must have been attacked first. + self:AddTransition("*", "AirbaseCaptured", "*") -- Airbase was captured by other coalition. + self:AddTransition("*", "AirbaseRecaptured", "*") -- Airbase was re-captured from other coalition. + self:AddTransition("*", "Destroyed", "Destoyed") -- Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. ------------------------ --- Pseudo Functions --- @@ -1876,7 +1876,7 @@ end -- @param #WAREHOUSE.Pendingitem request The request from which the assignment is extracted. -- @return #string The assignment text. function WAREHOUSE:GetAssignment(request) - return request.assignment + return tostring(request.assignment) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2030,7 +2030,7 @@ function WAREHOUSE:onafterStatus(From, Event, To) -- Check if any pending jobs are done and can be deleted from the self:_JobDone() - + -- Print status. self:_DisplayStatus() @@ -2074,12 +2074,14 @@ function WAREHOUSE:onafterStatus(From, Event, To) end ---- Function that checks if a pending job is done and can be removed from queue +--- Function that checks if a pending job is done and can be removed from queue. -- @param #WAREHOUSE self function WAREHOUSE:_JobDone() - -- Loop over all pending requests of this warehouse. + -- For jobs that are done, i.e. all cargo and transport assets are delivered, home or dead! local done={} + + -- Loop over all pending requests of this warehouse. for _,request in pairs(self.pending) do local request=request --#WAREHOUSE.Pendingitem @@ -2093,84 +2095,146 @@ function WAREHOUSE:_JobDone() local ntransport=0 if request.transportgroupset then ntransport=request.transportgroupset:Count() - end + end - if ncargo==0 and ntransport==0 then + local ncargotot=request.nasset + local ncargodelivered=request.ndelivered - --------------- - -- Job done! -- - --------------- - - self:E(string.format("Request id=%d done! No more cargo and transport assets alive.", request.uid)) - table.insert(done, request) - - elseif ncargo>0 and ntransport==0 and request.ntransport>0 then + -- Dead cargo: Ndead=Ntot-Ndeliverd-Nalive, + local ncargodead=ncargotot-ncargodelivered-ncargo - ----------------------------------- - -- Still cargo but no transports -- - ----------------------------------- - - -- Info message. - self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!", self.alias, request.uid, ncargo)) - -- All transports are dead but there is still cargo left ==> Put cargo back into stock. - for _,group in pairs(request.cargogroupset:GetSetObjects()) do - local group=group --Wrapper.Group#GROUP - - -- Check if group is alive. - if group and group:IsAlive() then - - if self.Debug then - group:SmokeRed() - end - - -- Add all assets back to stock - if group:IsPartlyOrCompletelyInZone(self.spawnzone) then - self:__AddAsset(5, group) - end - end - + local ntransporttot=request.ntransport + local ntransporthome=request.ntransporthome + + -- Dead transport: Ndead=Ntot-Nhome-Nalive. + local ntransportdead=ntransporttot-ntransporthome-ntransport + + local text=string.format("Request id=%d: Cargo: Ntot=%d, Nalive=%d, Ndelivered=%d, Ndead=%d | Transport: Ntot=%d, Nalive=%d, Nhome=%d, Ndead=%d", + request.uid, ncargotot, ncargo, ncargodelivered, ncargodead, ntransporttot, ntransport, ntransporthome, ntransportdead) + self:T(self.wid..text) + + + -- Handle different cases depending on what asset are still around. + if ncargo==0 then + --------------------- + -- Cargo delivered -- + --------------------- + + -- Trigger delivered event. + if not self.delivered[request.uid] then + self:Delivered(request) end - - elseif ncargo==0 and ntransport>0 then - - ----------------------------------- - -- No cargo but still transports -- - ----------------------------------- - - -- This is difficult! How do I know if transports were unused? They could also be just on their way back home. - - -- TODO: Handle this case. - if true then - return - end - - -- Info message. - self:_InfoMessage(string.format("Warehouse %s: No cargo assets left for request id=%s. Putting remaining %s transport assets back into warehouse!", self.alias, request.uid, ntransport)) - -- All transports are dead but there is still cargo left ==> Put cargo back into stock. - for _,group in pairs(request.transportgroupset:GetSetObjects()) do - local group=group --Wrapper.Group#GROUP + -- Check if transports are back home? + if ntransport==0 then + --------------- + -- Job done! -- + --------------- + + self:I(string.format("Request id=%d done! No more cargo or transport assets alive.", request.uid)) + table.insert(done, request) - -- Check if group is alive. - if group and group:IsAlive() then - - -- Assets back to stock. - if group:IsPartlyOrCompletelyInZone(self.spawnzone) then - - if self.Debug then - group:SmokeRed() + else + ----------------------------------- + -- No cargo but still transports -- + ----------------------------------- + + -- This is difficult! How do I know if transports were unused? They could also be just on their way back home. + + -- All transports are dead but there is still cargo left ==> Put cargo back into stock. + for _,_group in pairs(request.transportgroupset:GetSetObjects()) do + local group=_group --Wrapper.Group#GROUP + + -- Check if group is alive. + if group and group:IsAlive() then + + -- Check if group is in the spawn zone? + local category=group:GetCategory() + + -- Get current speed. + local speed=group:GetVelocityKMH() + local notmoving=speed<1 + + -- Closest airbase. + local airbase=group:GetCoordinate():GetClosestAirbase():GetName() + local athomebase=self.airbase and self.airbase:GetName()==airbase + + -- On ground + local onground=not group:InAir() + + -- In spawn zone. + local inspawnzone=group:IsPartlyOrCompletelyInZone(self.spawnzone) + + local ishome=false + if category==Group.Category.GROUND or category==Group.Category.HELICOPTER then + -- Units go back to the spawn zone, helicopters land and they should not move any more. + ishome=inspawnzone and onground and notmoving + elseif category==Group.Category.AIRPLANE then + -- Planes need to be on ground at their home airbase and should not move any more. + ishome=athomebase and onground and notmoving end - - self:__AddAsset(5, group) - end + + -- Debug text. + local text=string.format("Group %s: speed=%d km/h, onground=%s , airbase=%s, spawnzone=%s ==> ishome=%s", group:GetName(), speed, tostring(onground), airbase, tostring(inspawnzone), tostring(ishome)) + self:E(self.wid..text) + + if ishome then + + -- Info message. + self:_InfoMessage(string.format("Warehouse %s: No cargo assets left for request id=%s. Remaining %s transport assets go back into stock!", self.alias, request.uid, ntransport)) + + if self.Debug then + group:SmokeRed() + end + + -- Group arrived. + self:Arrived(group) + end + end end - + end - + + else + + if ntransport==0 and request.ntransport>0 then + ----------------------------------- + -- Still cargo but no transports -- + ----------------------------------- + + -- Info message. + self:_InfoMessage(string.format("Warehouse %s: All transports of request id=%s dead! Putting remaining %s cargo assets back into warehouse!", self.alias, request.uid, ncargo)) + + -- All transports are dead but there is still cargo left ==> Put cargo back into stock. + for _,_group in pairs(request.cargogroupset:GetSetObjects()) do + --local group=group --Wrapper.Group#GROUP + + -- These groups have been respawned as cargo, i.e. their name changed! + local groupname=_group:GetName() + local group=GROUP:FindByName(groupname.."#CARGO") + + -- Check if group is alive. + if group and group:IsAlive() then + + -- Check if group is in spawn zone? + if group:IsPartlyOrCompletelyInZone(self.spawnzone) then + -- Debug smoke. + if self.Debug then + group:SmokeBlue() + end + -- Add asset group back to stock. + --env.info(string.format("FF add asset group %s in function JobDone", group:GetName())) + self:AddAsset(group) + end + end + + end + end + end - end + end -- loop over requests -- Remove pending requests if done. for _,request in pairs(done) do @@ -2196,6 +2260,8 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu if type(group)=="string" then group=GROUP:FindByName(group) end + + --TODO: What happens if the name of the group is wrong? Saw strange behaviour! if group then @@ -2204,35 +2270,51 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu if wid and aid and rid then --------------------------- - -- This is a known asset -- + -- This is a KNOWN asset -- --------------------------- - if true then - - -- Get the asset from the global DB. - local asset=self:_FindAssetInDB(group) + -- Get the original warehouse this group belonged to. + local warehouse=self:_FindWarehouseInDB(wid) + if warehouse then + local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) + if request then - -- Note the group is only added once, i.e. the ngroups parameter is ignored here. - -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. - if asset~=nil then - self:_DebugMessage(string.format("Adding known asset uid=%d, attribute = %s to warehouse stock.", asset.uid, asset.attribute), 5) - table.insert(self.stock, asset) - else - self:_ErrorMessage(string.format("ERROR known asset could not be found in global warehouse db!"), 0) - end - - else - -- Request did not exist! - self:E("ERROR: Request does not exist in addAsset! This should not happen!") + -- Increase number of cargo delivered and transports home. + local istransport=warehouse:_GroupIsTransport(group,request) + if istransport==true then + request.ntransporthome=request.ntransporthome+1 + request.transportgroupset:Remove(group:GetName(), true) + self:I(warehouse.wid..string.format("FF Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) + elseif istransport==false then + request.ndelivered=request.ndelivered+1 + request.cargogroupset:Remove(self:_GetNameWithOut(group), true) + self:I(warehouse.wid..string.format("FF Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) + else + self:E(warehouse.wid..string.format("ERROR: Group %s is neither cargo nor transport!", group:GetName())) + end + + end end + -- Get the asset from the global DB. + local asset=self:_FindAssetInDB(group) + + -- Note the group is only added once, i.e. the ngroups parameter is ignored here. + -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. + if asset~=nil then + self:_DebugMessage(string.format("Warehouse %s: Adding KNOWN asset uid=%d with attribute=%s to stock.", self.alias, asset.uid, asset.attribute), 5) + table.insert(self.stock, asset) + else + self:_ErrorMessage(string.format("ERROR: Known asset could not be found in global warehouse db!"), 0) + end + else ------------------------- -- This is a NEW asset -- ------------------------- -- Debug info. - self:_DebugMessage(self.wid..string.format("Adding %d NEW assets of group %s to warehouse %s.", n, tostring(group:GetName()), self.alias), 5) + self:_DebugMessage(self.wid..string.format("Warehouse %s: Adding %d NEW assets of group %s to stock.", self.alias, n, tostring(group:GetName())), 5) -- This is a group that is not in the db yet. Add it n times. local assets=self:_RegisterAsset(group, n, forceattribute) @@ -2253,76 +2335,10 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu end -- Update status. - self:__Status(-1) + --self:__Status(-1) end ---- Update the pending requests by removing assets that have arrived. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group that has arrived at its destination. --- @return #WAREHOUSE.Pendingitem The updated request from the pending queue. --- @return #boolean If true, group is a cargo asset. If false, group is a transport asset. If nil, group is neither cargo nor transport. -function WAREHOUSE:_UpdatePending(group) - - -- Get request from group name. - local request=self:_GetRequestOfGroup(group, self.pending) - - -- Get the IDs for this group. In particular, we use the asset ID to figure out which group was delivered. - local wid,aid,rid=self:_GetIDsFromGroup(group) - - local isCargo=nil - - if request then - - -- If this request was already delivered. - if self.delivered[rid]==true then - - -- Loop over transport groups. - for _,_transportgroup in pairs(request.transportgroupset:GetSetObjects()) do - local transportgroup=_transportgroup --Wrapper.Group#GROUP - - -- IDs of cargo group. - local cwid,caid,crid=self:_GetIDsFromGroup(transportgroup) - - -- Remove group from transport group set and increase home counter. - if caid==aid then - request.transportgroupset:Remove(transportgroup:GetName()) - request.ntransporthome=request.ntransporthome+1 - env.info(string.format("Transport back home #%s", request.ntransporthome)) - isCargo=false - break - end - end - - else - - -- Loop over cargo groups. - for _,_cargogroup in pairs(request.cargogroupset:GetSetObjects()) do - local cargogroup=_cargogroup --Wrapper.Group#GROUP - - -- IDs of cargo group. - local cwid,caid,crid=self:_GetIDsFromGroup(cargogroup) - - -- Remove group from cargo group set and increase delivered counter. - if caid==aid then - request.cargogroupset:Remove(cargogroup:GetName()) - request.ndelivered=request.ndelivered+1 - env.info(string.format("FF delivered cargo # ", request.ndelivered)) - isCargo=true - break - end - end - - end - else - self:E(self.wid..string.format("WARNING: pending request could not be updated since request did not exist in pending queue!")) - end - - return request, isCargo -end - - - --- Find an asset in the the global warehouse db. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which it is assumed that it has a registered asset. @@ -2478,246 +2494,6 @@ function WAREHOUSE:_AssetItemInfo(asset) self:T3({Template=asset.template}) end - ---- Spawn a ground or naval asset in the corresponding spawn zone of the warehouse. --- @param #WAREHOUSE self --- @param #string alias Alias name of the asset group. --- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. --- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. --- @param Core.Zone#ZONE spawnzone Zone where the assets should be spawned. --- @param boolean aioff If true, AI of ground units are set to off. --- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aioff) - - if asset and (asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP) then - - -- Prepare spawn template. - local template=self:_SpawnAssetPrepareTemplate(asset, alias) - - -- Initial spawn point. - template.route.points[1]={} - - -- Get a random coordinate in the spawn zone. - local coord=spawnzone:GetRandomCoordinate() - - --spawnzone:SmokeZone(1, 30) - - -- Translate the position of the units. - for i=1,#template.units do - - -- Unit template. - local unit = template.units[i] - - -- Translate position. - local SX = unit.x or 0 - local SY = unit.y or 0 - local BX = asset.template.route.points[1].x - local BY = asset.template.route.points[1].y - local TX = coord.x + (SX-BX) - local TY = coord.z + (SY-BY) - - template.units[i].x = TX - template.units[i].y = TY - - end - - template.route.points[1].x = coord.x - template.route.points[1].y = coord.z - - template.x = coord.x - template.y = coord.z - template.alt = coord.y - - -- Spawn group. - local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP - - -- Activate group. Should only be necessary for late activated groups. - --group:Activate() - - -- Switch AI off if desired. This works only for ground and naval groups. - if aioff then - group:SetAIOff() - end - - return group - end - - return nil -end - ---- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse. --- @param #WAREHOUSE self --- @param #string alias Alias name of the asset group. --- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. --- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. --- @param #table parking Parking data for this asset. --- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. --- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off. --- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. -function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, hotstart) - - if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then - - -- Prepare the spawn template. - local template=self:_SpawnAssetPrepareTemplate(asset, alias) - - -- Set route points. - if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - - -- Get flight path if the group goes to another warehouse by itself. - template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) - - else - - -- Cold start (default). - local _type=COORDINATE.WaypointType.TakeOffParking - local _action=COORDINATE.WaypointAction.FromParkingArea - - -- Hot start. - if hotstart then - _type=COORDINATE.WaypointType.TakeOffParkingHot - _action=COORDINATE.WaypointAction.FromParkingAreaHot - end - - -- First route point is the warehouse airbase. - template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action, 0, true, self.airbase, nil, "Spawnpoint") - - end - - -- Get airbase ID and category. - local AirbaseID = self.airbase:GetID() - local AirbaseCategory = self.category - - -- Check enough parking spots. - if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then - --TODO Figure out what's necessary in this case. - - else - - if #parking<#template.units then - local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) - self:_DebugMessage(text) - return nil - end - - end - - -- Position the units. - for i=1,#template.units do - - -- Unit template. - local unit = template.units[i] - - if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then - - -- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense. - local coord=self.airbase:GetCoordinate() - - unit.x=coord.x - unit.y=coord.z - unit.alt=coord.y - - unit.parking_id = nil - unit.parking = nil - - else - - local coord=parking[i].Coordinate --Core.Point#COORDINATE - local terminal=parking[i].TerminalID --#number - - if self.Debug then - coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)) - end - - unit.x=coord.x - unit.y=coord.z - unit.alt=coord.y - - unit.parking_id = nil - unit.parking = terminal - - end - end - - -- And template position. - template.x = template.units[1].x - template.y = template.units[1].y - - -- DCS bug workaround. Spawning helos in uncontrolled state on carriers causes a big spash! - -- See https://forums.eagle.ru/showthread.php?t=219550 - if AirbaseCategory == Airbase.Category.SHIP and asset.category==Group.Category.HELICOPTER then - uncontrolled=false - end - - -- Uncontrolled spawning. - template.uncontrolled=uncontrolled - - -- Debug info. - self:T2({airtemplate=template}) - - -- Spawn group. - local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP - - -- Activate group - should only be necessary for late activated groups. - --group:Activate() - - return group - end - - return nil -end - - ---- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. --- @param #string alias Alias name of the group. --- @return #table Prepared new spawn template. -function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) - - -- Create an own copy of the template! - local template=UTILS.DeepCopy(asset.template) - - -- Set unique name. - template.name=alias - - -- Set current(!) coalition and country. - template.CoalitionID=self.coalition - template.CountryID=self.country - - -- Nillify the group ID. - template.groupId=nil - - -- For group units, visible needs to be false. - if asset.category==Group.Category.GROUND then - --template.visible=false - end - - -- No late activation. - template.lateActivation=false - - -- Set and empty route. - template.route = {} - template.route.routeRelativeTOT=true - template.route.points = {} - - -- Handle units. - for i=1,#template.units do - - -- Unit template. - local unit = template.units[i] - - -- Nillify the unit ID. - unit.unitId=nil - - -- Set unit name: -01, -02, ... - unit.name=string.format("%s-%02d", template.name , i) - - end - - return template -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On before "AddRequest" event. Checks some basic properties of the given parameters. @@ -3075,7 +2851,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Spawn Transport Groups -- ---------------------------- - -- Spawn the transport groups. + -- Spawn the transport groups. + env.info("FF Request ntransport = "..Request.ntransport) for i=1,Request.ntransport do -- Get stock item. @@ -3226,35 +3003,56 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Dispatcher Event Functions -- -------------------------------- - 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) + --- Function called after carrier picked up something. + function CargoTransport:OnAfterPickedUp(From, Event, To, Carrier, PickupZone) + + -- Get warehouse state. + local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + + -- Debug message. + local text=string.format("Carrier group %s picked up at pickup zone %s.", Carrier:GetName(), PickupZone:GetName()) + warehouse:T(warehouse.wid..text) + end - function CargoTransport:OnAfterDeployed(From,Event,To,CarrierGroup,DeployZone) - local text=string.format("FF Carrier group %s deployed event for deploy zone %s.", CarrierGroup:GetName(), DeployZone:GetName()) - env.info(text) + --- Function called if something was deployed. + function CargoTransport:OnAfterDeployed(From, Event, To, Carrier, DeployZone) + + -- Get warehouse state. + local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + + -- Debug message. + -- TODO: Depoloy zone is nil! + --local text=string.format("Carrier group %s deployed at deploy zone %s.", Carrier:GetName(), DeployZone:GetName()) + --warehouse:T(warehouse.wid..text) + end - function CargoTransport:OnAfterHome(From, Event, To, CarrierGroup, Coordinate, Speed, HomeZone) - local text=string.format("FF Carrier group %s going home to zone %s.", CarrierGroup:GetName(), HomeZone:GetName()) - env.info(text) + --- Function called if carrier group is going home. + function CargoTransport:OnAfterHome(From, Event, To, Carrier, Coordinate, Speed, HomeZone) + + -- Get warehouse state. + local warehouse=Carrier:GetState(Carrier, "WAREHOUSE") --#WAREHOUSE + + -- Debug message. + local text=string.format("Carrier group %s going home to zone %s.", Carrier:GetName(), HomeZone:GetName()) + warehouse:T(warehouse.wid..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", Carrier: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) + + -- Debug message. + local text=string.format("Carrier group %s loaded cargo %s into unit %s in pickup zone %s", Carrier:GetName(), Cargo:GetObject():GetName(), CarrierUnit:GetName(), PickupZone:GetName()) + warehouse:T(warehouse.wid..text) + -- Get cargo group object. + local group=Cargo:GetObject() --Wrapper.Group#GROUP + -- Get request. local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) @@ -3266,21 +3064,15 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --- Function called when cargo has arrived and was unloaded. function CargoTransport:OnAfterUnloaded(From, Event, To, Carrier, Cargo, CarrierUnit, DeployZone) - self:I("FF OnAfterUnloaded:") - self:I({From=From}) - self:I({Event=Event}) - self:I({To=To}) - self:I({Carrier=Carrier}) - self:I({Cargo=Cargo}) - - -- Get group obejet. - 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 unloaded from carrier %s.", tostring(group:GetName()), tostring(Carrier:GetName())) - env.info(text) + -- Get group obejet. + local group=Cargo:GetObject() --Wrapper.Group#GROUP + + -- Debug message. + local text=string.format("Cargo group %s was unloaded from carrier unit %s.", tostring(group:GetName()), tostring(CarrierUnit:GetName())) + warehouse:T(warehouse.wid..text) -- Load the cargo in the warehouse. --Cargo:Load(warehouse.warehouse) @@ -3304,9 +3096,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) MESSAGE:New(text, 5):ToAllIf(warehouse.Debug) warehouse:I(warehouse.wid..text) - -- Add carrier back to warehouse stock. Actual unit is destroyed. - --warehouse:AddAsset(Carrier) - -- Call arrived event for carrier. warehouse:__Arrived(1, Carrier) @@ -3317,108 +3106,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) end - ---- Spawns requested assets at warehouse or associated airbase. --- @param #WAREHOUSE self --- @param #WAREHOUSE.Queueitem Request Information table of the request. --- @return Core.Set#SET_GROUP Set of groups that were spawned. -function WAREHOUSE:_SpawnAssetRequest(Request) - self:E({requestUID=Request.uid}) - - -- Shortcut to cargo assets. - local _assetstock=Request.cargoassets - - -- General type and category. - local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute - local _cargocategory=Request.cargocategory --DCS#Group.Category - - -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. - local Parking={} - if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - Parking=self:_FindParkingForAssets(self.airbase,_assetstock) or {} - end - - -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. - local UnControlled=false - local AIOnOff=true - if Request.toself then - UnControlled=true - AIOnOff=false - end - - -- Create an empty group set. - local _groupset=SET_GROUP:New() - - -- Table for all spawned assets. - local _assets={} - - -- Loop over cargo requests. - for i=1,#_assetstock do - - -- Get stock item. - local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem - - -- Alias of the group. - local _alias=self:_Alias(_assetitem, Request) - - -- Spawn an asset group. - local _group=nil --Wrapper.Group#GROUP - if _assetitem.category==Group.Category.GROUND then - - -- Spawn ground troops. - _group=self:_SpawnAssetGroundNaval(_alias,_assetitem, Request, self.spawnzone) - - elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then - - -- Spawn air units. - if Parking[_assetitem.uid] then - _group=self:_SpawnAssetAircraft(_alias,_assetitem, Request, Parking[_assetitem.uid], UnControlled) - else - _group=self:_SpawnAssetAircraft(_alias,_assetitem, Request, nil, UnControlled) - end - - elseif _assetitem.category==Group.Category.TRAIN then - - -- Spawn train. - if self.rail then - --TODO: Rail should only get one asset because they would spawn on top! - --_group=_spawn:SpawnFromCoordinate(self.rail) - end - - self:E(self.wid.."ERROR: Spawning of TRAIN assets not possible yet!") - - elseif _assetitem.category==Group.Category.SHIP then - - -- Spawn naval assets. - _group=self:_SpawnAssetGroundNaval(_alias,_assetitem, Request, self.portzone) - - else - self:E(self.wid.."ERROR: Unknown asset category!") - end - - -- Add group to group set and asset list. - if _group then - _groupset:AddGroup(_group) - table.insert(_assets, _assetitem) - else - self:E(self.wid.."ERROR: Cargo asset could not be spawned!") - end - - end - - -- Delete spawned items from warehouse stock. - for _,_asset in pairs(_assets) do - local asset=_asset --#WAREHOUSE.Assetitem - Request.assets[asset.uid]=asset - self:_DeleteStockItem(asset) - end - - -- Overwrite the assets with the actually spawned ones. - Request.cargoassets=_assets - - return _groupset -end - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- On after "Unloaded" event. Triggered when a group was unloaded from the carrier. @@ -3486,8 +3173,14 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) -- Get the right warehouse to put the asset into -- Transports go back to the warehouse which called this function while cargo goes into the receiving warehouse. local warehouse=request.warehouse - if self:_GroupIsTransport(group,request) then + local istransport=self:_GroupIsTransport(group,request) + if istransport==true then warehouse=self + elseif istransport==false then + warehouse=request.warehouse + else + self:E(self.wid..string.format("ERROR: Group %s is neither cargo nor transport", group:GetName())) + return end -- Debug message. @@ -3497,9 +3190,25 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) if group:IsGround() and group:GetSpeedMax()>1 then group:RouteGroundTo(warehouse.coordinate, group:GetSpeedMax()*0.3, "Off Road") end + + -- Increase number of cargo delivered and transports home. + local istransport=warehouse:_GroupIsTransport(group,request) + if istransport==true then + request.ntransporthome=request.ntransporthome+1 + request.transportgroupset:Remove(group:GetName(), true) + self:I(warehouse.wid..string.format("FF Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) + elseif istransport==false then + request.ndelivered=request.ndelivered+1 + request.cargogroupset:Remove(self:_GetNameWithOut(group), true) + self:I(warehouse.wid..string.format("FF Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) + else + self:E(warehouse.wid..string.format("ERROR: Group %s is neither cargo nor transport!", group:GetName())) + end -- Move asset from pending queue into new warehouse. warehouse:__AddAsset(60, group) + env.info(string.format("FF add asset group %s in function onafterArrived in 60 seconds", group:GetName())) + --warehouse:AddAsset(group) end @@ -3507,7 +3216,7 @@ end --- Get asset from group and request. -- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group that has arrived at its destination. +-- @param Wrapper.Group#GROUP group The group for which the asset should be obtained. -- @param #WAREHOUSE.Pendingitem request Pending request. -- @return #WAREHOUSE.Assetitem The asset. function WAREHOUSE:_GetAssetFromGroupRequest(group,request) @@ -3787,6 +3496,352 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) self:__Stop(60) end +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Spawn functions +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Spawns requested assets at warehouse or associated airbase. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Queueitem Request Information table of the request. +-- @return Core.Set#SET_GROUP Set of groups that were spawned. +function WAREHOUSE:_SpawnAssetRequest(Request) + self:E({requestUID=Request.uid}) + + -- Shortcut to cargo assets. + local _assetstock=Request.cargoassets + + -- General type and category. + local _cargotype=Request.cargoattribute --#WAREHOUSE.Attribute + local _cargocategory=Request.cargocategory --DCS#Group.Category + + -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. + local Parking={} + if _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then + Parking=self:_FindParkingForAssets(self.airbase,_assetstock) or {} + end + + -- Spawn aircraft in uncontrolled state if request comes from the same warehouse. + local UnControlled=false + local AIOnOff=true + if Request.toself then + UnControlled=true + AIOnOff=false + end + + -- Create an empty group set. + local _groupset=SET_GROUP:New() + + -- Table for all spawned assets. + local _assets={} + + -- Loop over cargo requests. + for i=1,#_assetstock do + + -- Get stock item. + local _assetitem=_assetstock[i] --#WAREHOUSE.Assetitem + + -- Alias of the group. + local _alias=self:_Alias(_assetitem, Request) + + -- Spawn an asset group. + local _group=nil --Wrapper.Group#GROUP + if _assetitem.category==Group.Category.GROUND then + + -- Spawn ground troops. + _group=self:_SpawnAssetGroundNaval(_alias,_assetitem, Request, self.spawnzone) + + elseif _assetitem.category==Group.Category.AIRPLANE or _assetitem.category==Group.Category.HELICOPTER then + + -- Spawn air units. + if Parking[_assetitem.uid] then + _group=self:_SpawnAssetAircraft(_alias,_assetitem, Request, Parking[_assetitem.uid], UnControlled) + else + _group=self:_SpawnAssetAircraft(_alias,_assetitem, Request, nil, UnControlled) + end + + elseif _assetitem.category==Group.Category.TRAIN then + + -- Spawn train. + if self.rail then + --TODO: Rail should only get one asset because they would spawn on top! + --_group=_spawn:SpawnFromCoordinate(self.rail) + end + + self:E(self.wid.."ERROR: Spawning of TRAIN assets not possible yet!") + + elseif _assetitem.category==Group.Category.SHIP then + + -- Spawn naval assets. + _group=self:_SpawnAssetGroundNaval(_alias,_assetitem, Request, self.portzone) + + else + self:E(self.wid.."ERROR: Unknown asset category!") + end + + -- Add group to group set and asset list. + if _group then + _groupset:AddGroup(_group) + table.insert(_assets, _assetitem) + else + self:E(self.wid.."ERROR: Cargo asset could not be spawned!") + end + + end + + -- Delete spawned items from warehouse stock. + for _,_asset in pairs(_assets) do + local asset=_asset --#WAREHOUSE.Assetitem + Request.assets[asset.uid]=asset + self:_DeleteStockItem(asset) + end + + -- Overwrite the assets with the actually spawned ones. + Request.cargoassets=_assets + + return _groupset +end + + +--- Spawn a ground or naval asset in the corresponding spawn zone of the warehouse. +-- @param #WAREHOUSE self +-- @param #string alias Alias name of the asset group. +-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @param Core.Zone#ZONE spawnzone Zone where the assets should be spawned. +-- @param boolean aioff If true, AI of ground units are set to off. +-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. +function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, aioff) + + if asset and (asset.category==Group.Category.GROUND or asset.category==Group.Category.SHIP) then + + -- Prepare spawn template. + local template=self:_SpawnAssetPrepareTemplate(asset, alias) + + -- Initial spawn point. + template.route.points[1]={} + + -- Get a random coordinate in the spawn zone. + local coord=spawnzone:GetRandomCoordinate() + + --spawnzone:SmokeZone(1, 30) + + -- Translate the position of the units. + for i=1,#template.units do + + -- Unit template. + local unit = template.units[i] + + -- Translate position. + local SX = unit.x or 0 + local SY = unit.y or 0 + local BX = asset.template.route.points[1].x + local BY = asset.template.route.points[1].y + local TX = coord.x + (SX-BX) + local TY = coord.z + (SY-BY) + + template.units[i].x = TX + template.units[i].y = TY + + end + + template.route.points[1].x = coord.x + template.route.points[1].y = coord.z + + template.x = coord.x + template.y = coord.z + template.alt = coord.y + + -- Spawn group. + local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP + + -- Activate group. Should only be necessary for late activated groups. + --group:Activate() + + -- Switch AI off if desired. This works only for ground and naval groups. + if aioff then + group:SetAIOff() + end + + return group + end + + return nil +end + +--- Spawn an aircraft asset (plane or helo) at the airbase associated with the warehouse. +-- @param #WAREHOUSE self +-- @param #string alias Alias name of the asset group. +-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. +-- @param #WAREHOUSE.Queueitem request Request belonging to this asset. Needed for the name/alias. +-- @param #table parking Parking data for this asset. +-- @param #boolean uncontrolled Spawn aircraft in uncontrolled state. +-- @param #boolean hotstart Spawn aircraft with engines already on. Default is a cold start with engines off. +-- @return Wrapper.Group#GROUP The spawned group or nil if the group could not be spawned. +function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrolled, hotstart) + + if asset and asset.category==Group.Category.AIRPLANE or asset.category==Group.Category.HELICOPTER then + + -- Prepare the spawn template. + local template=self:_SpawnAssetPrepareTemplate(asset, alias) + + -- Set route points. + if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then + + -- Get flight path if the group goes to another warehouse by itself. + template.route.points=self:_GetFlightplan(asset, self.airbase, request.warehouse.airbase) + + else + + -- Cold start (default). + local _type=COORDINATE.WaypointType.TakeOffParking + local _action=COORDINATE.WaypointAction.FromParkingArea + + -- Hot start. + if hotstart then + _type=COORDINATE.WaypointType.TakeOffParkingHot + _action=COORDINATE.WaypointAction.FromParkingAreaHot + end + + -- First route point is the warehouse airbase. + template.route.points[1]=self.airbase:GetCoordinate():WaypointAir("BARO",_type,_action, 0, true, self.airbase, nil, "Spawnpoint") + + end + + -- Get airbase ID and category. + local AirbaseID = self.airbase:GetID() + local AirbaseCategory = self.category + + -- Check enough parking spots. + if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then + --TODO Figure out what's necessary in this case. + + else + + if #parking<#template.units then + local text=string.format("ERROR: Not enough parking! Free parking = %d < %d aircraft to be spawned.", #parking, #template.units) + self:_DebugMessage(text) + return nil + end + + end + + -- Position the units. + for i=1,#template.units do + + -- Unit template. + local unit = template.units[i] + + if AirbaseCategory == Airbase.Category.HELIPAD or AirbaseCategory == Airbase.Category.SHIP then + + -- Helipads we take the position of the airbase location, since the exact location of the spawn point does not make sense. + local coord=self.airbase:GetCoordinate() + + unit.x=coord.x + unit.y=coord.z + unit.alt=coord.y + + unit.parking_id = nil + unit.parking = nil + + else + + local coord=parking[i].Coordinate --Core.Point#COORDINATE + local terminal=parking[i].TerminalID --#number + + if self.Debug then + coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)) + end + + unit.x=coord.x + unit.y=coord.z + unit.alt=coord.y + + unit.parking_id = nil + unit.parking = terminal + + end + end + + -- And template position. + template.x = template.units[1].x + template.y = template.units[1].y + + -- DCS bug workaround. Spawning helos in uncontrolled state on carriers causes a big spash! + -- See https://forums.eagle.ru/showthread.php?t=219550 + if AirbaseCategory == Airbase.Category.SHIP and asset.category==Group.Category.HELICOPTER then + uncontrolled=false + end + + -- Uncontrolled spawning. + template.uncontrolled=uncontrolled + + -- Debug info. + self:T2({airtemplate=template}) + + -- Spawn group. + local group=_DATABASE:Spawn(template) --Wrapper.Group#GROUP + + -- Activate group - should only be necessary for late activated groups. + --group:Activate() + + return group + end + + return nil +end + + +--- Prepare a spawn template for the asset. Deep copy of asset template, adjusting template and unit names, nillifying group and unit ids. +-- @param #WAREHOUSE self +-- @param #WAREHOUSE.Assetitem asset Ground asset that will be spawned. +-- @param #string alias Alias name of the group. +-- @return #table Prepared new spawn template. +function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) + + -- Create an own copy of the template! + local template=UTILS.DeepCopy(asset.template) + + -- Set unique name. + template.name=alias + + -- Set current(!) coalition and country. + template.CoalitionID=self.coalition + template.CountryID=self.country + + -- Nillify the group ID. + template.groupId=nil + + -- For group units, visible needs to be false. + if asset.category==Group.Category.GROUND then + --template.visible=false + end + + -- No late activation. + template.lateActivation=false + + -- Set and empty route. + template.route = {} + template.route.routeRelativeTOT=true + template.route.points = {} + + -- Handle units. + for i=1,#template.units do + + -- Unit template. + local unit = template.units[i] + + -- Nillify the unit ID. + unit.unitId=nil + + -- Set unit name: -01, -02, ... + unit.name=string.format("%s-%02d", template.name , i) + + end + + return template +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Routing functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4061,6 +4116,7 @@ function WAREHOUSE:_OnEventLanding(EventData) -- Debug info. self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) + --[[ -- Check if all cargo was delivered. if self.delivered[rid]==true then @@ -4081,6 +4137,7 @@ function WAREHOUSE:_OnEventLanding(EventData) end end + ]] end end @@ -4157,75 +4214,51 @@ end -- @param #WAREHOUSE self -- @param Core.Event#EVENTDATA EventData Event data. function WAREHOUSE:_OnEventCrashOrDead(EventData) - self:T3(self.wid..string.format("Warehouse %s captured event dead or crash!",self.alias)) + self:T3(self.wid..string.format("Warehouse %s captured event dead or crash!", self.alias)) - if EventData and EventData.IniUnit and EventData.IniGroup then + if EventData then - -- Check if warehouse was destroyed. - local warehousename=self.warehouse:GetName() - if EventData.IniUnitName==warehousename then - self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!", warehousename, self.alias)) - - -- Trigger Destroyed event. - self:Destroyed() + -- Check if warehouse was destroyed. We compare the name of the destroyed unit. + if EventData.IniUnitName then + local warehousename=self.warehouse:GetName() + if EventData.IniUnitName==warehousename then + self:_DebugMessage(string.format("Warehouse %s alias %s was destroyed!", warehousename, self.alias)) + + -- Trigger Destroyed event. + self:Destroyed() + end end - -- Check if an asset unit was destroyed. - local group=EventData.IniGroup - - -- Get warehouse, asset and request IDs from the group name. - local wid,aid,rid=self:_GetIDsFromGroup(group) - - -- Check that we have the right warehouse. - if wid==self.uid then - - -- Debug message. - self:T(self.wid..string.format("Warehouse %s captured event dead or crash of its asset unit %s.", self.alias, EventData.IniUnitName)) + --self:I(self.wid..string.format("Warehouse %s captured event dead or crash or unit %s.", self.alias, tostring(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 - - -- This is the right request. - if request.uid==rid then - - -- Update cargo and transport group sets of this request. We need to know if this job is finished. - self:_UnitDead(EventData.IniUnit, request) + -- Check if an asset unit was destroyed. + if EventData.IniGroup then - -- Update pending request. Increase ndelivered/ntransporthome and delete group from corresponding group set. - self:_UpdatePending(group) - - -- Number of cargo assets still in group set. - if isCargo==true then + -- Group initiating the event. + local group=EventData.IniGroup + + -- Get warehouse, asset and request IDs from the group name. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + -- Check that we have the right warehouse. + if wid==self.uid then + + -- Debug message. + self:T(self.wid..string.format("Warehouse %s captured event dead or crash of its asset unit %s.", self.alias, EventData.IniUnitName)) - -- Current size of cargo group set. - local ncargo=request.cargogroupset:Count() - - -- Debug message. - local text=string.format("Cargo %d of %s added to warehouse %s stock. Assets still to deliver %d.", - request.ndelivered, tostring(request.nasset), request.warehouse.alias, ncargo) - self:_DebugMessage(text, 5) - - -- All cargo delivered. - if ncargo==0 then - self:Delivered(request) - end + -- 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 - elseif isCargo==false then - - -- Current size of cargo group set. - local ntransport=request.transportgroupset:Count() - - -- Debug message. - local text=string.format("Transport %d of %s added to warehouse %s stock. Transports still missing %d.", - request.ntransporthome, tostring(request.ntransport), request.warehouse.alias, ntransport) - self:_DebugMessage(text, 5) - - end + -- This is the right request. + if request.uid==rid then + -- Update cargo and transport group sets of this request. We need to know if this job is finished. + self:_UnitDead(EventData.IniUnit, request) + + end end - - end + end end end end @@ -4236,24 +4269,49 @@ end -- @param Wrapper.Unit#UNIT deadunit Unit that died. -- @param #WAREHOUSE.Pendingitem request Request that needs to be updated. function WAREHOUSE:_UnitDead(deadunit, request) + + -- Flare unit + deadunit:FlareRed() -- 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 + local groupdead=true + local nunits=0 + local nunits0=0 if group then - local nunits=group:GetSize() - -- One (or less) units in group. - if nunits<=1 then - isgroupdead=true + -- Get current size of group and substract the unit that just died because it is not counted yet! + nunits=group:GetSize()-1 + nunits0=group:GetInitialSize() + + if nunits > 0 then + groupdead=false 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) + -- Here I need to get rid of the #CARGO at the end to obtain the original name again! + local unitname=self:_GetNameWithOut(deadunit) + local groupname=self:_GetNameWithOut(group) + + + -- Debug message. + local text=string.format("Unit %s died! #units=%d/%d ==> Group dead=%s (IsAlive=%s).", unitname, nunits, nunits0, tostring(groupdead), tostring(group:IsAlive())) + self:T2(self.wid..text) + + -- Check if this really works as expected! + if nunits<0 then + self:E(self.wid.."ERROR: Number of units negative! This should not happen.") + end + + if groupdead then + self:T(self.wid..string.format("Group %s (transport=%s) is dead!", groupname, tostring(self:_GroupIsTransport(group,request)))) + group:SmokeWhite() + end + + + -- Not sure what this does actually and if it would be better to set it to true. local NoTriggerEvent=false if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -4263,8 +4321,9 @@ function WAREHOUSE:_UnitDead(deadunit, request) --- -- Remove dead group from carg group set. - if isgroupdead==true then - request.cargogroupset:Remove(group:GetName(), NoTriggerEvent) + if groupdead==true then + request.cargogroupset:Remove(groupname, NoTriggerEvent) + self:T(self.wid..string.format("Removed selfpropelled cargo %s: ncargo=%d.", groupname, request.cargogroupset:Count())) end else @@ -4274,44 +4333,59 @@ function WAREHOUSE:_UnitDead(deadunit, request) -- 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 + + -- Check if this a cargo or transport group. + local istransport=self:_GroupIsTransport(group,request) + + if istransport==true then -- Get the carrier unit table holding the cargo groups inside this carrier. - local carrierunit=request.carriercargo[deadunit:GetName()] + local cargogroups=request.carriercargo[unitname] - if carrierunit then + if cargogroups then - -- Loop over all groups inside the carrier ==> all dead. - for _,cargogroup in pairs(carrierunit) do - request.cargogroupset:Remove(cargogroup:GetName(),NoTriggerEvent) + -- Loop over all groups inside the destroyed carrier ==> all dead. + for _,cargogroup in pairs(cargogroups) do + local cargoname=self:_GetNameWithOut(cargogroup) + request.cargogroupset:Remove(cargoname, NoTriggerEvent) + self:T(self.wid..string.format("Removed transported cargo %s inside dead carrier %s: ncargo=%d", cargoname, unitname, request.cargogroupset:Count())) end end -- Whole carrier group is dead. Remove it from the carrier group set. - if isgroupdead then - request.transportcargoset:Remove(group:GetName(), NoTriggerEvent) + if groupdead then + request.transportgroupset:Remove(groupname, NoTriggerEvent) + self:T(self.wid..string.format("Removed transport %s: ntransport=%d", groupname, request.transportgroupset:Count())) end - else + elseif istransport==false then -- 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:GetName(), NoTriggerEvent) - - -- TODO: This as well? + if groupdead==true then + request.cargogroupset:Remove(groupname, NoTriggerEvent) + self:T(self.wid..string.format("Removed transported cargo %s outside carrier: ncargo=%d", groupname, request.cargogroupset:Count())) + -- This as well? --request.transportcargoset:RemoveCargosByName(RemoveCargoNames) - end + end + else + self:E(self.wid..string.format("ERROR: Group %s is neither cargo nor transport!", group:GetName())) end end + end +--- Remove group. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group to be removed. +function WAREHOUSE:_RemoveGroup(group) +end + +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Warehouse event handling function. -- Handles the case when the airbase associated with the warehous is captured. @@ -4788,15 +4862,18 @@ function WAREHOUSE:_CheckRequestNow(request) return false end + -- If no transport is requested, assets need to be mobile unless it is a self request. local onlymobile=false - if request.ntransport==0 and not request.toself then + if type(request.transport)=="number" and request.ntransport==0 and not request.toself then onlymobile=true end -- Check if number of requested assets is in stock. local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset, onlymobile) + local _transports + -- Check if enough assets are in stock. if not _enough then local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) @@ -4827,18 +4904,8 @@ function WAREHOUSE:_CheckRequestNow(request) end - -- Set chosen assets. + -- Add this here or gettransport fails request.cargoassets=_assets - request.cargoattribute=_assetattribute - request.cargocategory=_assetcategory - - -- Debug info: - local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n", request.cargoattribute, request.cargocategory) - for _i,_asset in pairs(_assets) do - local asset=_asset --#WAREHOUSE.Assetitem - text=text..string.format("%d) asset name=%s, type=%s, category=%d, #units=%d",_i, asset.templatename, asset.unittype, asset.category, asset.nunits) - end - self:T(self.wid..text) end @@ -4846,7 +4913,7 @@ function WAREHOUSE:_CheckRequestNow(request) if request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then -- Get best transports for this asset pack. - local _transports=self:_GetTransportsForAssets(request) + _transports=self:_GetTransportsForAssets(request) -- Check if at least one transport asset is available. if #_transports>0 then @@ -4865,19 +4932,6 @@ function WAREHOUSE:_CheckRequestNow(request) return false end end - - -- Set chosen assets. - request.transportassets=_transports - request.transportattribute=_transportattribute - request.transportcategory=_transportcategory - - -- Debug info: - local text=string.format("Selected transport assets, attibute=%s, category=%d:\n", request.transportattribute, request.transportcategory) - for _i,_asset in pairs(_assets) do - local asset=_asset --#WAREHOUSE.Assetitem - text=text..string.format("%d) asset name=%s, type=%s, category=%d, #units=%d",_i, asset.templatename, asset.unittype, asset.category, asset.nunits) - end - self:T(self.wid..text) else @@ -4893,6 +4947,39 @@ function WAREHOUSE:_CheckRequestNow(request) -- Self propelled case. Nothing to do for now. end + + + -- Set chosen cargo assets. + request.cargoassets=_assets + request.cargoattribute=_assets[1].attribute + request.cargocategory=_assets[1].category + request.nasset=#_assets + + -- Debug info: + local text=string.format("Selected cargo assets, attibute=%s, category=%d:\n", request.cargoattribute, request.cargocategory) + for _i,_asset in pairs(_assets) do + local asset=_asset --#WAREHOUSE.Assetitem + text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d",_i, asset.templatename, asset.unittype, asset.category, asset.nunits) + end + self:T(self.wid..text) + + if request.transporttype ~= WAREHOUSE.TransportType.SELFPROPELLED then + + -- Set chosen transport assets. + request.transportassets=_transports + request.transportattribute=_transports[1].attribute + request.transportcategory=_transports[1].category + request.ntransport=#_transports + + -- Debug info: + local text=string.format("Selected transport assets, attibute=%s, category=%d:\n", request.transportattribute, request.transportcategory) + for _i,_asset in pairs(_transports) do + local asset=_asset --#WAREHOUSE.Assetitem + text=text..string.format("%d) name=%s, type=%s, category=%d, #units=%d\n",_i, asset.templatename, asset.unittype, asset.category, asset.nunits) + end + self:T(self.wid..text) + + end return true end @@ -4969,10 +5056,12 @@ function WAREHOUSE:_GetTransportsForAssets(request) for j,asset in pairs(cargoassets) do -- How many times does the cargo fit into the carrier? - local n=cargobay/asset.weight + local delta=cargobay-asset.weight + + --self:E(self.wid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) -- Cargo fits into carrier - if n>=1 then + if delta>0 then -- Reduce remaining cargobay. cargobay=cargobay-asset.weight self:T3(self.wid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) @@ -4982,6 +5071,8 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- This transport group is used. used=true + else + env.info("FF not used! n="..delta) end end -- loop over assets @@ -4992,7 +5083,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) local nput=putintocarrier[j] local cargo=cargoassets[nput] - self:T2(self.wid..string.format("Cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) + self:T(self.wid..string.format("Cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) table.remove(cargoassets, nput) end @@ -5002,8 +5093,14 @@ function WAREHOUSE:_GetTransportsForAssets(request) table.insert(used_transports, transport) end + -- Convert relative quantity (all, half) to absolute number if necessary. + local ntrans=self:_Rel2AbsQuantity(request.ntransport, #transports) + env.info("FF ntrans = "..ntrans) + env.info("FF #trans = "..#transports) + -- Max number of transport groups reached? - if #used_transports >= request.ntransport then + if #used_transports >= ntrans then + request.ntransport=#used_transports break end end @@ -5027,6 +5124,37 @@ function WAREHOUSE:_GetTransportsForAssets(request) return used_transports end +---Relative to absolute quantity. +-- @param #WAREHOUSE self +-- @param #string relative Relative number in terms of @{#WAREHOUSE.Quantity}. +-- @param #number ntot Total number. +-- @return #number Absolute number. +function WAREHOUSE:_Rel2AbsQuantity(relative, ntot) + + local nabs=0 + + -- Handle string input for nmax. + if type(relative)=="string" then + if relative==WAREHOUSE.Quantity.ALL then + nabs=ntot + elseif relative==WAREHOUSE.Quantity.THREEQUARTERS then + nabs=ntot*3/4 + elseif relative==WAREHOUSE.Quantity.HALF then + nabs=ntot/2 + elseif relative==WAREHOUSE.Quantity.THIRD then + nabs=ntot/3 + elseif relative==WAREHOUSE.Quantity.QUARTER then + nabs=ntot/4 + else + nabs=math.min(1, ntot) + end + else + nabs=relative + end + + return nabs +end + ---Sorts the queue and checks if the request can be fulfilled. -- @param #WAREHOUSE self -- @return #WAREHOUSE.Queueitem Chosen request. @@ -5316,15 +5444,16 @@ end --- Is the group a used as transporter for a given request? -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which the info is gathered. --- @param #WAREHOUSE.Pendingitem request Request --- @return #WAREHOUSE.Pendingitem The request belonging to this group. +-- @param #WAREHOUSE.Pendingitem request Request. +-- @return #boolean True if group is transport, false if group is cargo and nil otherwise. function WAREHOUSE:_GroupIsTransport(group, request) + -- Name of the group under question. + local groupname=self:_GetNameWithOut(group) + if request.transportgroupset then - local transporters=request.transportgroupset:GetSetObjects() - local groupname=group:GetName() for _,transport in pairs(transporters) do if transport:GetName()==groupname then return true @@ -5332,7 +5461,17 @@ function WAREHOUSE:_GroupIsTransport(group, request) end end - return false + if request.cargogroupset then + local cargos=request.cargogroupset:GetSetObjects() + + for _,cargo in pairs(cargos) do + if self:_GetNameWithOut(cargo)==groupname then + return false + end + end + end + + return nil end @@ -5360,6 +5499,24 @@ function WAREHOUSE:_alias(unittype, wid, aid, qid) return _alias end +--- Get group name without any spawn or cargo suffix #CARGO etc. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which the info is gathered. +-- @return #string Name of the object without trailing #... +function WAREHOUSE:_GetNameWithOut(group) + if group then + local name=group:GetName() + local namewithout=UTILS.Split(name, "#")[1] + if namewithout then + return namewithout + else + return name + end + end + return group:GetName() +end + + --- Get warehouse id, asset id and request id from group name (alias). -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group from which the info is gathered. @@ -5758,7 +5915,7 @@ function WAREHOUSE:_PrintQueue(queue, name) -- Output text: text=text..string.format( - "\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%d / #alive=%s / #home=%s", + "\n%d) UID=%d, Prio=%d, Clock=%s, Assignment=%s | Requestor=%s [Airbase=%s, category=%d] | Assets(%s)=%s: #requested=%s / #alive=%s / #delivered=%s | Transport=%s: #requested=%s / #alive=%s / #home=%s", i, uid, prio, clock, assignment, requestor, airbasename, requestorAirbaseCat, assetdesc, assetdescval, nasset, ncargogroupset, ndelivered, transporttype, ntransport, ntransportalive, ntransporthome) end From 3d0f1faadc0326701ff4464c3d2b588da1b9023b Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 12 Sep 2018 20:43:24 +0200 Subject: [PATCH 59/73] Warehouse v0.4.3 - Cleaned up tracing output. - Added home zone for airplane dispatcher. - Fixed bugs in relative quantities. - Added parking check for transports. --- Moose Development/Moose/Core/Event.lua | 2 +- .../Moose/Functional/Warehouse.lua | 225 +++++++++++------- 2 files changed, 144 insertions(+), 83 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 765e3b2ff..ec43201e1 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -979,7 +979,7 @@ function EVENT:onEvent( Event ) local PriorityEnd = PriorityOrder == -1 and 1 or 5 if Event.IniObjectCategory ~= Object.Category.STATIC then - self:E( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) + self:T( { EventMeta.Text, Event, Event.IniDCSUnitName, Event.TgtDCSUnitName, PriorityOrder } ) end for EventPriority = PriorityBegin, PriorityEnd, PriorityOrder do diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 8aa6dbb82..d5793b360 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -991,21 +991,22 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.2" +WAREHOUSE.version="0.4.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- DONE: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. +-- TODO: Check overlapping aircraft sometimes. -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Test capturing a neutral warehouse. -- TODO: Make more examples: ARTY, CAP, ... -- TODO: Check also general requests like all ground. Is this a problem for self propelled if immobile units are among the assets? Check if transport. --- TODO: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- TODO: Handle the case when units of a group die during the transfer. -- TODO: Added habours as interface for transport to from warehouses? -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua! +-- DONE: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. Should be done now! +-- DONE: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- DONE: Write documentation. -- DONE: Add AAA, SAMs and UAVs to generalized attributes. -- DONE: Add warehouse quantity enumerator. @@ -2131,8 +2132,16 @@ function WAREHOUSE:_JobDone() --------------- -- Job done! -- --------------- - - self:I(string.format("Request id=%d done! No more cargo or transport assets alive.", request.uid)) + + -- Info on job. + local text=string.format("Warehouse %s: Job on request id=%d done!\n", self.alias, request.uid) + text=text..string.format("- %d of %d assets delivered to %s. Casualties %d.", ncargodelivered, ncargotot, request.warehouse.alias, ncargodead) + if request.ntransport>0 then + text=text..string.format("\n- %d of %d transports returned home. Casualties %d.", ntransporthome, ntransporttot, ntransportdead) + end + self:_InfoMessage(text, 20) + + -- Mark request for deletion. table.insert(done, request) else @@ -2141,6 +2150,7 @@ function WAREHOUSE:_JobDone() ----------------------------------- -- This is difficult! How do I know if transports were unused? They could also be just on their way back home. + -- ==> Need to do a lot of checks. -- All transports are dead but there is still cargo left ==> Put cargo back into stock. for _,_group in pairs(request.transportgroupset:GetSetObjects()) do @@ -2224,7 +2234,6 @@ function WAREHOUSE:_JobDone() group:SmokeBlue() end -- Add asset group back to stock. - --env.info(string.format("FF add asset group %s in function JobDone", group:GetName())) self:AddAsset(group) end end @@ -2261,7 +2270,6 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu group=GROUP:FindByName(group) end - --TODO: What happens if the name of the group is wrong? Saw strange behaviour! if group then @@ -2284,13 +2292,13 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu if istransport==true then request.ntransporthome=request.ntransporthome+1 request.transportgroupset:Remove(group:GetName(), true) - self:I(warehouse.wid..string.format("FF Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) + self:T3(warehouse.wid..string.format("Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) elseif istransport==false then request.ndelivered=request.ndelivered+1 request.cargogroupset:Remove(self:_GetNameWithOut(group), true) - self:I(warehouse.wid..string.format("FF Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) + self:T3(warehouse.wid..string.format("Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) else - self:E(warehouse.wid..string.format("ERROR: Group %s is neither cargo nor transport!", group:GetName())) + self:T(warehouse.wid..string.format("WARNING: Group %s is neither cargo nor transport!", group:GetName())) end end @@ -2329,13 +2337,15 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Destroy group if it is alive. if group:IsAlive()==true then self:_DebugMessage(string.format("Destroying group %s.", group:GetName()), 5) - group:Destroy(true) + group:Destroy() end - + + else + self:E(self.wid.."ERROR: Unknown group added as asset!") end -- Update status. - --self:__Status(-1) + self:__Status(-1) end @@ -2666,7 +2676,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Delete request from queue because it will never be possible. --TODO: Unless(!) this is a moving warehouse which could, e.g., be an aircraft carrier. - self:_DeleteQueueItem(Request, self.queue) + --self:_DeleteQueueItem(Request, self.queue) return false end @@ -2852,7 +2862,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) ---------------------------- -- Spawn the transport groups. - env.info("FF Request ntransport = "..Request.ntransport) for i=1,Request.ntransport do -- Get stock item. @@ -2949,20 +2958,19 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) if Request.transporttype==WAREHOUSE.TransportType.AIRPLANE then - -- Pickup and deloay zones. + -- Pickup and deploy zones. local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) -- Define dispatcher for this task. CargoTransport = AI_CARGO_DISPATCHER_AIRPLANE:New(TransportSet, CargoGroups, PickupAirbaseSet, DeployAirbaseSet) + + -- Set home zone. + CargoTransport:SetHomeZone(ZONE_AIRBASE:New(self.airbase:GetName())) elseif Request.transporttype==WAREHOUSE.TransportType.HELICOPTER then - -- Pickup and deloay zones. - --local PickupAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(self.airbase:GetName())) - --local DeployAirbaseSet = SET_ZONE:New():AddZone(ZONE_AIRBASE:New(Request.airbase:GetName())) - - -- Pickup and deloay zones. + -- Pickup and deploy zones. local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone) local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) @@ -2979,7 +2987,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif Request.transporttype==WAREHOUSE.TransportType.APC then - -- Pickup and deloay zones. + -- Pickup and deploy zones. local PickupZoneSet = SET_ZONE:New():AddZone(self.spawnzone) local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.spawnzone) @@ -2994,7 +3002,6 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) CargoTransport:SetPickupRadius(self.spawnzone:GetRadius(), 20) CargoTransport:SetDeployRadius(Request.warehouse.spawnzone:GetRadius(), 20) - else self:E(self.wid.."ERROR: Unknown transporttype!") end @@ -3196,20 +3203,17 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) if istransport==true then request.ntransporthome=request.ntransporthome+1 request.transportgroupset:Remove(group:GetName(), true) - self:I(warehouse.wid..string.format("FF Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) + self:T2(warehouse.wid..string.format("Transport %d of %s returned home.", request.ntransporthome, tostring(request.ntransport))) elseif istransport==false then request.ndelivered=request.ndelivered+1 request.cargogroupset:Remove(self:_GetNameWithOut(group), true) - self:I(warehouse.wid..string.format("FF Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) + self:T2(warehouse.wid..string.format("Cargo %d of %s delivered.", request.ndelivered, tostring(request.nasset))) else self:E(warehouse.wid..string.format("ERROR: Group %s is neither cargo nor transport!", group:GetName())) end -- Move asset from pending queue into new warehouse. warehouse:__AddAsset(60, group) - env.info(string.format("FF add asset group %s in function onafterArrived in 60 seconds", group:GetName())) - --warehouse:AddAsset(group) - end end @@ -4183,20 +4187,28 @@ function WAREHOUSE:_OnEventArrived(EventData) -- If all IDs are good we can assume it is a warehouse asset. if wid~=nil and aid~=nil and rid~=nil then - + -- Check that warehouse ID is right. if self.uid==wid then + + local request=self:_GetRequestOfGroup(group, self.pending) + local istransport=self:_GroupIsTransport(group,request) + + -- Check that group is cargo and not transport. + if istransport==false then - -- Debug info. - local text=string.format("Air asset group %s from warehouse %s arrived at its destination.", group:GetName(), self.alias) - self:_InfoMessage(text) - - -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. - -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since - -- some units might still be taxiing or whatever. Therefore, we add 10 seconds for each additional unit of the group until the first arrived event is triggered. - local nunits=#group:GetUnits() - local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. - self:__Arrived(dt, group) + -- Debug info. + local text=string.format("Air asset group %s from warehouse %s arrived at its destination.", group:GetName(), self.alias) + self:_InfoMessage(text) + + -- Trigger arrived event for this group. Note that each unit of a group will trigger this event. So the onafterArrived function needs to take care of that. + -- Actually, we only take the first unit of the group that arrives. If it does, we assume the whole group arrived, which might not be the case, since + -- some units might still be taxiing or whatever. Therefore, we add 10 seconds for each additional unit of the group until the first arrived event is triggered. + local nunits=#group:GetUnits() + local dt=10*(nunits-1)+1 -- one unit = 1 sec, two units = 11 sec, three units = 21 sec before we call the group arrived. + self:__Arrived(dt, group) + + end end @@ -4445,8 +4457,6 @@ function WAREHOUSE:_CheckConquered() local radius=self.zone:GetRadius() -- Scan units in zone. - --TODO: need to check if scan radius does what it should! - -- It seems to return units that are further away than the radius. local gotunits,_,_,units,_,_=coord:ScanObjects(radius, true, false, false) local Nblue=0 @@ -4639,12 +4649,22 @@ function WAREHOUSE:_CheckRequestValid(request) -- Check if number of requested assets is in stock. local _assets,_nassets,_enough=self:_FilterStock(self.stock, request.assetdesc, request.assetdescval, request.nasset) - + -- No assets in stock? Checks cannot be performed. if #_assets==0 then return true end + -- Convert relative to absolute number if necessary. + local nasset=request.nasset + if type(request.nasset)=="string" then + nasset=self:_QuantityRel2Abs(request.nasset,_nassets) + end + + -- Debug check, request.nasset might be a string Quantity enumerator. + local text=string.format("Request valid? Number of assets: requested=%s=%d, selected=%d, total=%d, enough=%s.", tostring(request.nasset), nasset,#_assets,_nassets, tostring(_enough)) + self:T(text) + -- First asset. Is representative for all filtered items in stock. local asset=_assets[1] --#WAREHOUSE.Assetitem @@ -4714,15 +4734,14 @@ function WAREHOUSE:_CheckRequestValid(request) -- Not enough parking at sending warehouse. --if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then - if np_departure < request.nasset then - self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots = %d.", termtype, np_departure)) + if np_departure < nasset then + self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.", termtype, np_departure, nasset)) valid=false end - -- Not enough parking at requesting warehouse. - --if np_destination < request.nasset then - if np_destination == 0 then -- TODO: maybe this is just right for FAPS/SHIPS - self:E(string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at requesting warehouse. Available spots = %d.", termtype, np_destination)) + -- No parking at requesting warehouse. + if np_destination == 0 then + self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse. Available spots = %d!", termtype, np_destination)) valid=false end @@ -4834,6 +4853,57 @@ function WAREHOUSE:_CheckRequestValid(request) self:E("ERROR: Incorrect request. Transport type unknown!") valid=false end + + -- Airborne assets: check parking situation. + if request.transporttype==WAREHOUSE.TransportType.AIRPLANE or request.transporttype==WAREHOUSE.TransportType.HELICOPTER then + + -- Check if number of requested assets is in stock. + local _assets,_nassets,_enough=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype, request.ntransport) + + -- Convert relative to absolute number if necessary. + local nasset=request.ntransport + if type(request.nasset)=="string" then + nasset=self:_QuantityRel2Abs(request.ntransport,_nassets) + end + + -- Debug check, request.nasset might be a string Quantity enumerator. + local text=string.format("Request valid? Number of transports: requested=%s=%d, selected=%d, total=%d, enough=%s.", tostring(request.ntransport), nasset,#_assets,_nassets, tostring(_enough)) + self:T(text) + + -- Get necessary terminal type for helos or transport aircraft. + local termtype=self:_GetTerminal(request.transporttype) + + -- Get number of parking spots. + local np_departure=self.airbase:GetParkingSpotsNumber(termtype) + + -- Debug info. + self:T(self.wid..string.format("Transport attribute = %s, terminal type = %d, spots at departure = %d.", request.transporttype, termtype, np_departure)) + + -- Not enough parking at sending warehouse. + --if (np_departure < request.nasset) and not (self.category==Airbase.Category.SHIP or self.category==Airbase.Category.HELIPAD) then + if np_departure < nasset then + self:E(self.wid..string.format("ERROR: Incorrect request. Not enough parking spots of terminal type %d at warehouse. Available spots %d < %d necessary.", termtype, np_departure, nasset)) + valid=false + end + + -- Planes also need parking at the receiving warehouse. + if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then + + -- Total number of parking spots for transport planes at destination. + local np_destination=request.airbase:GetParkingSpotsNumber(termtype) + + -- Debug info. + self:T(self.wid..string.format("Transport attribute = %s: total # of spots (type=%d) at destination = %d.", asset.attribute, termtype, np_destination)) + + -- No parking at requesting warehouse. + if np_destination == 0 then + self:E(string.format("ERROR: Incorrect request. No parking spots of terminal type %d at requesting warehouse for transports. Available spots = %d!", termtype, np_destination)) + valid=false + end + end + + end + end @@ -4841,7 +4911,7 @@ function WAREHOUSE:_CheckRequestValid(request) if valid==false then self:E(self.wid..string.format("ERROR: Got invalid request id=%d.", request.uid)) else - self:T3(self.wid..string.format("Got valid request id=%d.", request.uid)) + self:T3(self.wid..string.format("Request id=%d valid :)", request.uid)) end return valid @@ -5054,6 +5124,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- Loop over cargo assets. for j,asset in pairs(cargoassets) do + local asset=asset --#WAREHOUSE.Assetitem -- How many times does the cargo fit into the carrier? local delta=cargobay-asset.weight @@ -5071,21 +5142,26 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- This transport group is used. used=true - else - env.info("FF not used! n="..delta) + else + self:T2(self.wid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be not used! Cargo bay - asset weight = %d kg", transport.templatename, asset.templatename, delta)) end end -- loop over assets end -- loop over units - -- Remove cargo assets from list. Needs to be done back-to-front in oder not to confuse the loop. + -- Remove cargo assets from list. Needs to be done back-to-front in order not to confuse the loop. for j=#putintocarrier,1, -1 do + local nput=putintocarrier[j] - local cargo=cargoassets[nput] - self:T(self.wid..string.format("Cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) - table.remove(cargoassets, nput) + -- Need to check if multiple units in a group and the group has already been removed! + -- TODO: This might need to be improved but is working okay so far. + if cargo then + -- Remove this group because it was used. + self:T2(self.wid..string.format("Cargo id=%d assigned for carrier id=%d", cargo.uid, transport.uid)) + table.remove(cargoassets, nput) + end end -- Cargo was assined for this carrier. @@ -5094,9 +5170,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) end -- Convert relative quantity (all, half) to absolute number if necessary. - local ntrans=self:_Rel2AbsQuantity(request.ntransport, #transports) - env.info("FF ntrans = "..ntrans) - env.info("FF #trans = "..#transports) + local ntrans=self:_QuantityRel2Abs(request.ntransport, #transports) -- Max number of transport groups reached? if #used_transports >= ntrans then @@ -5129,7 +5203,7 @@ end -- @param #string relative Relative number in terms of @{#WAREHOUSE.Quantity}. -- @param #number ntot Total number. -- @return #number Absolute number. -function WAREHOUSE:_Rel2AbsQuantity(relative, ntot) +function WAREHOUSE:_QuantityRel2Abs(relative, ntot) local nabs=0 @@ -5138,19 +5212,21 @@ function WAREHOUSE:_Rel2AbsQuantity(relative, ntot) if relative==WAREHOUSE.Quantity.ALL then nabs=ntot elseif relative==WAREHOUSE.Quantity.THREEQUARTERS then - nabs=ntot*3/4 + nabs=UTILS.Round(ntot*3/4) elseif relative==WAREHOUSE.Quantity.HALF then - nabs=ntot/2 + nabs=UTILS.Round(ntot/2) elseif relative==WAREHOUSE.Quantity.THIRD then - nabs=ntot/3 + nabs=UTILS.Round(ntot/3) elseif relative==WAREHOUSE.Quantity.QUARTER then - nabs=ntot/4 + nabs=UTILS.Round(ntot/4) else nabs=math.min(1, ntot) end else nabs=relative end + + self:T2(self.wid..string.format("Relative %s: tot=%d, abs=%.2f", tostring(relative), ntot, nabs)) return nabs end @@ -5187,7 +5263,7 @@ function WAREHOUSE:_CheckQueue() if okay and valid and not gotit then request=qitem gotit=true - --break + break end end @@ -5282,7 +5358,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local obstacles={} -- Loop over all parking spots and get the obstacles. - -- TODO: How long does this take on very large airbases, i.e. those with hundereds of parking spots? + -- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay! for _,parkingspot in pairs(parkingdata) do -- Coordinate of the parking spot. @@ -5309,7 +5385,6 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local _vec3=static:getPoint() local _coord=COORDINATE:NewFromVec3(_vec3) local _name=static:getName() - --env.info("FF static name = "..tostring(_name)) local _size=self:_GetObjectSize(static) table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="static"}) end @@ -5613,22 +5688,8 @@ function WAREHOUSE:_FilterStock(stock, descriptor, attribute, nmax, mobile) return filtered, ntot, false end - -- Handle string input for nmax. - if type(nmax)=="string" then - if nmax:lower()==WAREHOUSE.Quantity.ALL then - nmax=ntot - elseif nmax==WAREHOUSE.Quantity.THREEQUARTERS then - nmax=ntot*3/4 - elseif nmax==WAREHOUSE.Quantity.HALF then - nmax=ntot/2 - elseif nmax==WAREHOUSE.Quantity.THIRD then - nmax=ntot/3 - elseif nmax==WAREHOUSE.Quantity.QUARTER then - nmax=ntot/4 - else - nmax=math.min(1, ntot) - end - end + -- Convert relative to absolute number if necessary. + nmax=self:_QuantityRel2Abs(nmax,ntot) -- Loop over stock items. for _i,_asset in ipairs(stock) do From 54ae3ed62bdd0c4082319f51a11f90332a9c82ff Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Wed, 12 Sep 2018 23:04:39 +0200 Subject: [PATCH 60/73] Warehouse v0.4.4 - Fixed little bug in warehouse. - Added home event function to cargo airplane. Planes are now going home after job is done. - Added false option for :Destroy() to generate no event. - Added false parameter to respawn function. --- .../Moose/AI/AI_Cargo_Airplane.lua | 44 +++++++++++++++---- .../Moose/Functional/Warehouse.lua | 4 +- Moose Development/Moose/Wrapper/Group.lua | 4 +- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua index 8081a88de..afbd3bdda 100644 --- a/Moose Development/Moose/AI/AI_Cargo_Airplane.lua +++ b/Moose Development/Moose/AI/AI_Cargo_Airplane.lua @@ -251,7 +251,6 @@ function AI_CARGO_AIRPLANE:onafterLanded( Airplane, From, Event, To ) -- Aircraft was sent to this airbase to pickup troops. Initiate loadling. if self.RoutePickup == true then - env.info("FF load airplane "..Airplane:GetName()) self:Load( self.PickupZone ) self.RoutePickup = false self.Relocating = true @@ -281,15 +280,15 @@ end -- @param Core.Zone#ZONE_AIRBASE PickupZone function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, Speed, PickupZone ) - if Airplane and Airplane:IsAlive()~=nil then - env.info("FF onafterpick aircraft alive") + if Airplane and Airplane:IsAlive() then + --env.info("FF onafterpick aircraft alive") self.PickupZone = PickupZone -- Get closest airbase of current position. local ClosestAirbase, DistToAirbase=Airplane:GetCoordinate():GetClosestAirbase() - env.info("FF onafterpickup closest airbase "..ClosestAirbase:GetName()) + --env.info("FF onafterpickup closest airbase "..ClosestAirbase:GetName()) -- Two cases. Aircraft spawned in air or at an airbase. if Airplane:InAir() then @@ -298,15 +297,16 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, self.Airbase=ClosestAirbase end + -- Set pickup airbase. local Airbase = PickupZone:GetAirbase() -- 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) + --env.info("Distance closest to pickup airbase = "..Dist) if Airplane:InAir() or Dist>500 then - env.info("FF onafterpickup routing to airbase "..ClosestAirbase:GetName()) + --env.info("FF onafterpickup routing to airbase "..ClosestAirbase:GetName()) -- Route aircraft to pickup airbase. self:Route( Airplane, Airbase, Speed ) @@ -318,7 +318,7 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, self.RoutePickup = true else - env.info("FF onafterpick calling landed") + --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 @@ -329,7 +329,7 @@ function AI_CARGO_AIRPLANE:onafterPickup( Airplane, From, Event, To, Coordinate, self.Transporting = false self.Relocating = true else - env.info("FF onafterpick aircraft not alive") + --env.info("FF onafterpick aircraft not alive") end @@ -447,7 +447,7 @@ end -- @param #boolean Uncontrolled If true, spawn group in uncontrolled state. function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Uncontrolled ) - if Airplane and Airplane:IsAlive()~=nil then + if Airplane and Airplane:IsAlive() then -- Set takeoff type. local Takeoff = SPAWN.Takeoff.Cold @@ -510,3 +510,29 @@ function AI_CARGO_AIRPLANE:Route( Airplane, Airbase, Speed, Uncontrolled ) end end end + +--- On after Home event. Aircraft will be routed to their home base. +-- @param #AI_CARGO_AIRPLANE self +-- @param Wrapper.Group#GROUP Airplane The cargo plane. +-- @param From From state. +-- @param Event Event. +-- @param To To State. +-- @param Core.Point#COORDINATE Coordinate Home place (not used). +-- @param #number Speed Speed in km/h to fly to the home airbase (zone). Default is 80% of max possible speed the unit can go. +-- @param Core.Zone#ZONE_AIRBASE HomeZone The home airbase (zone) where the plane should return to. +function AI_CARGO_AIRPLANE:onafterHome(Airplane, From, Event, To, Coordinate, Speed, HomeZone ) + if Airplane and Airplane:IsAlive() then + + -- We are going home! + self.RouteHome = true + + -- Home Base. + local HomeBase=HomeZone:GetAirbase() + self.Airbase=HomeBase + + -- Now route the airplane home + self:Route(Airplane, HomeBase, Speed) + + end + +end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index d5793b360..8c6bf8909 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -991,7 +991,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.3" +WAREHOUSE.version="0.4.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -4862,7 +4862,7 @@ function WAREHOUSE:_CheckRequestValid(request) -- Convert relative to absolute number if necessary. local nasset=request.ntransport - if type(request.nasset)=="string" then + if type(request.ntransport)=="string" then nasset=self:_QuantityRel2Abs(request.ntransport,_nassets) end diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index d65f6e86c..d839315cb 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -288,6 +288,8 @@ function GROUP:Destroy( GenerateEvent ) else self:CreateEventDead( timer.getTime(), UnitData ) end + elseif GenerateEvent==false then + -- Do nothing! else self:CreateEventRemoveUnit( timer.getTime(), UnitData ) end @@ -1524,7 +1526,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- SpawnTemplate.uncontrolled=Uncontrolled -- Destroy old group. - self:Destroy() + self:Destroy(false) --SCHEDULER:New(nil, DATABASE.Spawn, {_DATABASE, SpawnTemplate}, 0.00001) From 956d4fa248b8a67df1f1b4860352661a853c1407 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Thu, 13 Sep 2018 16:16:33 +0200 Subject: [PATCH 61/73] Warehouse v0.4.4w --- .../Moose/Functional/Warehouse.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 8c6bf8909..61bff02e4 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -506,7 +506,7 @@ -- -- Start Warehouse Berlin. -- warehouse.Berlin:Start() -- --- -- Warehouse Berlin requests 10 infantry groups and 3 APCs from warehouse Batumi. +-- -- Warehouse Berlin requests 10 infantry groups and 5 APCs from warehouse Batumi. -- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 10) -- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_APC, 5) -- @@ -991,7 +991,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.4" +WAREHOUSE.version="0.4.4w" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1243,7 +1243,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. -- @param Wrapper.Group#GROUP group Group that has arrived. - --- On after "Arrived" event user function. Called when a groups has arrived at its destination. + --- On after "Arrived" event user function. Called when a group has arrived at its destination. -- @function [parent=#WAREHOUSE] OnAfterArrived -- @param #WAREHOUSE self -- @param #string From From state. @@ -1302,15 +1302,15 @@ function WAREHOUSE:New(warehouse, alias) --- Triggers the FSM event "Attacked" when a warehouse is under attack by an another coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] Attacked - -- @param DCS#coalition.side Coalition which is attacking the warehouse. - -- @param DCS#country.id Country which is attacking the warehouse. + -- @param DCS#coalition.side Coalition Coalition side which is attacking the warehouse, i.e. a number of @{DCS#coalition.side} enumerator. + -- @param DCS#country.id Country Country ID, which is attacking the warehouse, i.e. a number @{DCS#country.id} enumerator. --- Triggers the FSM event "Attacked" with a delay when a warehouse is under attack by an another coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] __Attacked -- @param #number delay Delay in seconds. - -- @param DCS#coalition.side Coalition which is attacking the warehouse. - -- @param DCS#country.id Country which is attacking the warehouse. + -- @param DCS#coalition.side Coalition Coalition side which is attacking the warehouse, i.e. a number of @{DCS#coalition.side} enumerator. + -- @param DCS#country.id Country Country ID, which is attacking the warehouse, i.e. a number @{DCS#country.id} enumerator. --- On after "Attacked" event user function. Called when a warehouse (zone) is under attack by an enemy. -- @param #WAREHOUSE self @@ -1318,8 +1318,8 @@ function WAREHOUSE:New(warehouse, alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param DCS#coalition.side Coalition which is attacking the warehouse. - -- @param DCS#country.id Country which is attacking the warehouse. + -- @param DCS#coalition.side Coalition Coalition side which is attacking the warehouse, i.e. a number of @{DCS#coalition.side} enumerator. + -- @param DCS#country.id Country Country ID, which is attacking the warehouse, i.e. a number @{DCS#country.id} enumerator. --- Triggers the FSM event "Defeated" when an attack from an enemy was defeated. From c7c63b37fe0a10fb8c78303dc1cc90a3feb7311d Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Fri, 14 Sep 2018 15:25:06 +0200 Subject: [PATCH 62/73] Warehouse v0.4.4w --- Moose Development/Moose/DCS.lua | 5 +- .../Moose/Functional/Warehouse.lua | 430 ++++++++++-------- .../Moose/Wrapper/Identifiable.lua | 13 +- 3 files changed, 257 insertions(+), 191 deletions(-) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index c8e997f5c..8caec3753 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -208,9 +208,6 @@ do -- country -- @type country -- @field #country.id id - --- [DCS Enum country](https://wiki.hoggitworld.com/view/DCS_enum_country) - -- @field #country - country = {} --- [DCS enumerator country](https://wiki.hoggitworld.com/view/DCS_enum_country) -- @type country.id @@ -289,6 +286,8 @@ do -- country -- @field OMAN -- @field UNITED_ARAB_EMIRATES + country = {} --#country + end -- country diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 61bff02e4..9b37cea88 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -26,14 +26,10 @@ -- @field #boolean Debug If true, send debug messages to all. -- @field #boolean Report If true, send status messages to coalition. -- @field Wrapper.Static#STATIC warehouse The phyical warehouse structure. --- @field DCS#coalition.side coalition Coalition side the warehouse belongs to. --- @field DCS#country.id country Country ID the warehouse belongs to. -- @field #string alias Alias of the warehouse. Name its called when sending messages. -- @field Core.Zone#ZONE zone Zone around the warehouse. If this zone is captured, the warehouse and all its assets goes to the capturing coaliton. -- @field Wrapper.Airbase#AIRBASE airbase Airbase the warehouse belongs to. -- @field #string airbasename Name of the airbase associated to the warehouse. --- @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.Point#COORDINATE road Closest point to warehouse on road. -- @field Core.Point#COORDINATE rail Closest point to warehouse on rail. -- @field Core.Zone#ZONE spawnzone Zone in which assets are spawned. @@ -89,10 +85,10 @@ -- ## What means of transportation are available? -- Firstly, all mobile assets can be send from warehouse to another on their own. -- --- * Ground vehicles will use the road infrastructure. So a good road connection for both warehouses is important. +-- * Ground vehicles will use the road infrastructure. So a good road connection for both warehouses is important but also off road connections can be added if necessary. -- * Airborne units get a flightplan from the airbase of the sending warehouse to the airbase of the receiving warehouse. This already implies that for airborne -- assets both warehouses need an airbase. If either one of the warehouses does not have an associated airbase, direct transportation of airborne assest is not possible. --- * Naval units can be exchanged between warehouses which posses a port/habour. Also shipping lanes must be specified manually but the user since DCS does not provide these. +-- * Naval units can be exchanged between warehouses which possess a port, which can be defined by the user. Also shipping lanes must be specified manually but the user since DCS does not provide these. -- * Trains (would) use the available railroad infrastructure and both warehouses must have a connection to the railroad. Unfortunately, however, trains are not yet implemented to -- a reasonable degree in DCS at the moment and hence cannot be used yet. -- @@ -103,7 +99,8 @@ -- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} and -- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}. -- --- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. +-- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. Howver, note that +-- the dispatchers as of yet cannot use user defined off road paths for example since they are classes of their own and use a different routing logic. -- -- === -- @@ -298,7 +295,7 @@ -- In order to use airborne assets, a warehouse needs to have an associated airbase. This can be an airdrome, a FARP/HELOPAD or a ship. -- -- If there is an airbase within 3 km range of the warehouse it is automatically set as the associated airbase. A user can set an airbase manually --- with the @{#WAREHOUSE.SetAirbase} function. Keep in mind, that sometimes, ground units need to walk/drive from the spawn zone to the airport +-- with the @{#WAREHOUSE.SetAirbase} function. Keep in mind that sometimes ground units need to walk/drive from the spawn zone to the airport -- to get to their transport carriers. -- -- ## Naval Connections @@ -633,8 +630,8 @@ -- -- -- Enemy has entered the warehouse zone. This triggers the "Attacked" event. -- function warehouse.Senaki:OnAfterAttacked(From,Event,To,Coalition,Country) --- MESSAGE:New(string.format("Warehouse %s: We are under attack!", self.alias), 30):ToCoalition(self.coalition) --- self.coordinate:SmokeRed() +-- MESSAGE:New(string.format("Warehouse %s: We are under attack!", self.alias), 30):ToCoalition(self:GetCoalition()) +-- self:GetCoordinate():SmokeRed() -- end -- -- -- Now the red BMP also captured the warehouse. So the warehouse and the airbase are both red and planes can be spawned again. @@ -695,7 +692,11 @@ -- -- ## Example 10: Aircraft Carrier - Rescue Helo and Escort -- --- This example shows how to spawn assets from a warehouse located on an aircraft carrier. +-- This example shows how to spawn assets from a warehouse located on an aircraft carrier. The warehouse must still be represented by a +-- physical static object. However, on a carrier space is limit so we take a smaller static. In priciple one could also take something +-- like a windsock. +-- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Carrier.png) -- -- After 10 seconds we make a self request for a rescue helicopter. Note, that the @{#WAREHOUSE.AddRequest} function has a parameter which lets you -- specify an "Assignment". This can be later used to identify the request and take the right actions. @@ -710,7 +711,9 @@ -- a fresh helo. Effectively, there we created an infinite, never ending loop. So a rescue helo will be up at all times. -- -- After 30 and 45 seconds requests for five groups of armed speedboats are made. These will be spawned in the port zone right behind the carrier. --- The first five groups will go port of the carrier an form a left wing formation. The seconds groups will to the analogue on the starboard side. +-- The first five groups will go port of the carrier an form a left wing formation. The seconds groups will to the analogue on the starboard side. +-- **Note** that in order to spawn naval assets a warehouse needs a port (zone). Since the carrier and hence the warehouse is mobile, we define a moving +-- zone as @{Core.Zone#ZONE_UNIT} with the carrier as reference unit. The "port" of the Stennis at its stern so all naval assets are spawned behing the carrier. -- -- -- Start warehouse on USS Stennis. -- warehouse.Stennis:Start() @@ -734,17 +737,20 @@ -- local groupset=groupset --Core.Set#SET_GROUP -- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem -- +-- -- USS Stennis is the mother ship. -- local Mother=UNIT:FindByName("Stennis") -- --- if request.assignment=="Speedboats Left" then +-- -- Get assignment for this request. +-- local assignment=warehouse.Stennis:GetAssignment(request) +-- +-- if assignment=="Speedboats Left" then -- -- -- Define AI Formation object. -- -- Note that this has to be a global variable or the garbage collector will remove it for some reason! --- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Left Formation with Carrier", "Follow Carrier at given parameters.") +-- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Port Formation with Carrier", "Follow Carrier at given parameters.") -- --- -- Formation parameters. --- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50) --- +-- -- Formation parameters and start. +-- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50) -- CarrierFormationLeft:__Start(2) -- -- for _,group in pairs(groupset:GetSetObjects()) do @@ -752,41 +758,33 @@ -- group:FlareRed() -- end -- --- elseif request.assignment=="Speedboats Right" then +-- elseif assignment=="Speedboats Right" then -- -- -- Define AI Formation object. -- -- Note that this has to be a global variable or the garbage collector will remove it for some reason! --- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Right Formation with Carrier", "Follow Carrier at given parameters.") +-- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Starboard Formation with Carrier", "Follow Carrier at given parameters.") -- --- -- Formation parameters. +-- -- Formation parameters and start. -- CarrierFormationRight:FormationRightWing(200 ,50, 0, 0, 500, 50) +-- CarrierFormationRight:__Start(2) -- --- CarrierFormationRight:__Start(2) --- --- for _,group in pairs(groupset:GetSetObjects()) do --- local group=group --Wrapper.Group#GROUP --- group:FlareGreen() --- end --- --- elseif request.assignment=="Rescue Helo" then +-- elseif assignment=="Rescue Helo" then -- -- -- Define AI Formation object. -- CarrierFormationHelo = AI_FORMATION:New(Mother, groupset, "Helo Formation with Carrier", "Follow Carrier at given parameters.") -- --- -- Formation parameters. +-- -- Formation parameters and start. -- CarrierFormationHelo:FormationCenterWing(-150, 50, 20, 50, 100, 50) --- --- -- Start formation FSM. -- CarrierFormationHelo:__Start(2) -- -- end -- --- --- When the helo is out of fuel, it will return to the carrier and should be delivered. +-- --- When the helo is out of fuel, it will return to the carrier. The asset is considered as delivered. -- function warehouse.Stennis:OnAfterDelivered(From,Event,To,request) -- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem -- -- -- So we start another request. --- if request.assignment=="Rescue Helo" then +-- if warehouse.Stennis:GetAssignment(request)=="Rescue Helo" then -- warehouse.Stennis:__AddRequest(10, warehouse.Stennis, WAREHOUSE.Descriptor.TEMPLATENAME, "CH-53E", 1, nil, nil, nil, "Rescue Helo") -- end -- end @@ -800,14 +798,10 @@ WAREHOUSE = { Debug = false, Report = true, warehouse = nil, - coalition = nil, - country = nil, alias = nil, zone = nil, airbase = nil, airbasename = nil, - category = -1, - coordinate = nil, road = nil, rail = nil, spawnzone = nil, @@ -1052,7 +1046,12 @@ WAREHOUSE.version="0.4.4w" -- @param #string alias (Optional) Alias of the warehouse, i.e. the name it will be called when sending messages etc. Default is the name of the static -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) - BASE:E({warehouse=warehouse:GetName()}) + BASE:T({warehouse=warehouse}) + + -- Check if just a string was given and convert to static. + if type(warehouse)=="string" then + warehouse=STATIC:FindByName(warehouse, true) + end -- Nil check. if warehouse==nil then @@ -1075,16 +1074,12 @@ function WAREHOUSE:New(warehouse, alias) -- Set some variables. self.warehouse=warehouse self.uid=tonumber(warehouse:GetID()) - self.coalition=warehouse:GetCoalition() - self.country=warehouse:GetCountry() - self.coordinate=warehouse:GetCoordinate() -- Closest of the same coalition but within a certain range. - local _airbase=self.coordinate:GetClosestAirbase(nil, self.coalition) - if _airbase and _airbase:GetCoordinate():Get2DDistance(self.coordinate) < 3000 then + local _airbase=self:GetCoordinate():GetClosestAirbase(nil, self:GetCoalition()) + if _airbase and _airbase:GetCoordinate():Get2DDistance(self:GetCoordinate()) < 3000 then self.airbase=_airbase self.airbasename=self.airbase:GetName() - self.category=self.airbase:GetDesc().category end -- Define warehouse and default spawn zone. @@ -1174,14 +1169,14 @@ function WAREHOUSE:New(warehouse, alias) -- @param #number delay Delay in seconds. - --- Trigger the FSM event "AddAsset". Add an airplane group to the warehouse stock. + --- Trigger the FSM event "AddAsset". Add a group to the warehouse stock. -- @function [parent=#WAREHOUSE] AddAsset -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. -- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. - --- Trigger the FSM event "AddAsset" with a delay. Add an airplane group to the warehouse stock. + --- Trigger the FSM event "AddAsset" with a delay. Add a group to the warehouse stock. -- @function [parent=#WAREHOUSE] __AddAsset -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. @@ -1228,7 +1223,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE.Queueitem Request Information table of the request. - --- Triggers the FSM event "Arrived", i.e. when a group has arrived at the destination warehouse. + --- Triggers the FSM event "Arrived" when a group has arrived at the destination warehouse. -- This function should always be called from the sending and not the receiving warehouse. -- If the group is a cargo asset, it is added to the receiving warehouse. If the group is a transporter it -- is added to the sending warehouse since carriers are supposed to return to their home warehouse once @@ -1237,7 +1232,10 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group Group that has arrived. - --- Triggers the FSM event "Arrived" after a delay, i.e. when a group has arrived at the destination. + --- Triggers the FSM event "Arrived" after a delay when a group has arrived at the destination. + -- This function should always be called from the sending and not the receiving warehouse. + -- If the group is a cargo asset, it is added to the receiving warehouse. If the group is a transporter it + -- is added to the sending warehouse since carriers are supposed to return to their home warehouse once -- @function [parent=#WAREHOUSE] __Arrived -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. @@ -1272,7 +1270,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE.Pendingitem request Pending request that was now delivered. - --- Triggers the FSM event "SelfRequest". Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. + --- Triggers the FSM event "SelfRequest". Request was initiated from the warehouse to itself. Groups are just spawned at the warehouse or the associated airbase. -- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, -- this request is used to put the groups back into the warehouse stock. -- @function [parent=#WAREHOUSE] SelfRequest @@ -1280,7 +1278,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. -- @param #WAREHOUSE.Pendingitem request Pending self request. - --- Triggers the FSM event "SelfRequest" with a delay. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. + --- Triggers the FSM event "SelfRequest" with a delay. Request was initiated from the warehouse to itself. Groups are just spawned at the warehouse or the associated airbase. -- If the warehouse is currently under attack when the self request is made, the self request is added to the defending table. One the attack is defeated, -- this request is used to put the groups back into the warehouse stock. -- @function [parent=#WAREHOUSE] __SelfRequest @@ -1289,7 +1287,27 @@ function WAREHOUSE:New(warehouse, alias) -- @param Core.Set#SET_GROUP groupset The set of cargo groups that was delivered to the warehouse itself. -- @param #WAREHOUSE.Pendingitem request Pending self request. - --- On after "SelfRequest" event. Request was initiated to the warehouse itself. Groups are just spawned at the warehouse or the associated airbase. + --- On after "SelfRequest" event. Request was initiated from the warehouse to itself. Groups are simply spawned at the warehouse or the associated airbase. + -- All requested assets are passed as a @{Core.Set#SET_GROUP} and can be used for further tasks or in other MOOSE classes. + -- Note that airborne assets are spawned in uncontrolled state so they do not simply "fly away" after spawning. + -- + -- @usage + -- --- Self request event. Triggered once the assets are spawned in the spawn zone or at the airbase. + -- function mywarehouse:OnAfterSelfRequest(From, Event, To, groupset, request) + -- local groupset=groupset --Core.Set#SET_GROUP + -- + -- -- Loop over all groups spawned from that request. + -- for _,group in pairs(groupset:GetSetObjects()) do + -- local group=group --Wrapper.Group#GROUP + -- + -- -- Gree smoke on spawned group. + -- group:SmokeGreen() + -- + -- -- Activate uncontrolled airborne group if necessary. + -- group:StartUncontrolled() + -- end + -- end + -- -- @function [parent=#WAREHOUSE] OnAfterSelfRequest -- @param #WAREHOUSE self -- @param #string From From state. @@ -1325,15 +1343,11 @@ function WAREHOUSE:New(warehouse, alias) --- Triggers the FSM event "Defeated" when an attack from an enemy was defeated. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] Defeated - -- @param DCS#coalition.side Coalition which is attacking the warehouse. - -- @param DCS#country.id Country which is attacking the warehouse. --- Triggers the FSM event "Defeated" with a delay when an attack from an enemy was defeated. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] __Defeated -- @param #number delay Delay in seconds. - -- @param DCS#coalition.side Coalition which is attacking the warehouse. - -- @param DCS#country.id Country which is attacking the warehouse. --- On after "Defeated" event user function. Called when an enemy attack was defeated. -- @param #WAREHOUSE self @@ -1341,8 +1355,6 @@ function WAREHOUSE:New(warehouse, alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param DCS#coalition.side Coalition which is attacking the warehouse. - -- @param DCS#country.id Country which is attacking the warehouse. --- Triggers the FSM event "Captured" when a warehouse has been captured by another coalition. @@ -1358,26 +1370,26 @@ function WAREHOUSE:New(warehouse, alias) -- @param DCS#coalition.side Coalition which captured the warehouse. -- @param DCS#country.id Country which has captured the warehouse. - --- On after "Captured" event user function. Called when the warehouse has been captured by an enemy coalition. + --- On after "Captured" event user function. Called when the warehouse has been captured by an enemy coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] OnAfterCaptured -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param DCS#coalition.side Coalition which captured the warehouse. - -- @param DCS#country.id Country which has captured the warehouse. + -- @param DCS#coalition.side Coalition Coalition side which captured the warehouse, i.e. a number of @{DCS#coalition.side} enumerator. + -- @param DCS#country.id Country Country id which has captured the warehouse, i.e. a number @{DCS#country.id} enumerator. -- --- Triggers the FSM event "AirbaseCaptured" when the airbase of the warehouse has been captured by another coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] AirbaseCaptured - -- @param DCS#coalition.side Coalition which captured the airbase. + -- @param DCS#coalition.side Coalition Coalition side which captured the airbase, i.e. a number of @{DCS#coalition.side} enumerator. --- Triggers the FSM event "AirbaseCaptured" with a delay when the airbase of the warehouse has been captured by another coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] __AirbaseCaptured -- @param #number delay Delay in seconds. - -- @param DCS#coalition.side Coalition which captured the airbase. + -- @param DCS#coalition.side Coalition Coalition side which captured the airbase, i.e. a number of @{DCS#coalition.side} enumerator. --- On after "AirbaseCaptured" even user function. Called when the airbase of the warehouse has been captured by another coalition. -- @param #WAREHOUSE self @@ -1385,19 +1397,19 @@ function WAREHOUSE:New(warehouse, alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param DCS#coalition.side Coalition which captured the airbase. + -- @param DCS#coalition.side Coalition Coalition side which captured the airbase, i.e. a number of @{DCS#coalition.side} enumerator. --- Triggers the FSM event "AirbaseRecaptured" when the airbase of the warehouse has been re-captured from the other coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] AirbaseRecaptured - -- @param DCS#coalition.side Coalition which re-captured the airbase. + -- @param DCS#coalition.side Coalition Coalition which re-captured the airbase, i.e. the same as the current warehouse owner coalition. --- Triggers the FSM event "AirbaseRecaptured" with a delay when the airbase of the warehouse has been re-captured from the other coalition. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] __AirbaseRecaptured -- @param #number delay Delay in seconds. - -- @param DCS#coalition.side Coalition which re-captured the airbase. + -- @param DCS#coalition.side Coalition Coalition which re-captured the airbase, i.e. the same as the current warehouse owner coalition. --- On after "AirbaseRecaptured" event user function. Called when the airbase of the warehouse has been re-captured from the other coalition. -- @param #WAREHOUSE self @@ -1405,7 +1417,7 @@ function WAREHOUSE:New(warehouse, alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. - -- @param DCS#coalition.side Coalition which re-captured the airbase. + -- @param DCS#coalition.side Coalition Coalition which re-captured the airbase, i.e. the same as the current warehouse owner coalition. --- Triggers the FSM event "Destroyed" when the warehouse was destroyed. All services are stopped. @@ -1463,7 +1475,7 @@ function WAREHOUSE:SetReportOff() return self end ---- Set interval of status updates. Note that only one request can be processed per time interval. +--- Set interval of status updates. Note that normally only one request can be processed per time interval. -- @param #WAREHOUSE self -- @param #number timeinterval Time interval in seconds. -- @return #WAREHOUSE self @@ -1515,6 +1527,7 @@ end -- @return #WAREHOUSE self function WAREHOUSE:SetAirbase(airbase) self.airbase=airbase + self.airbasename=airbase:GetName() return self end @@ -1588,7 +1601,7 @@ function WAREHOUSE:AddShippingLane(remotewarehouse, group, oneway) for i=1,#lane do local coord=lane[i] --Core.Point#COORDINATE local text=string.format("Shipping lane %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) - coord:MarkToCoalition(text, self.coalition) + coord:MarkToCoalition(text, self:GetCoalition()) end end @@ -1634,7 +1647,7 @@ function WAREHOUSE:AddOffRoadPath(remotewarehouse, group, oneway) for i=1,#path do local coord=path[i] --Core.Point#COORDINATE local text=string.format("Off road path from %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) - coord:MarkToCoalition(text, self.coalition) + coord:MarkToCoalition(text, self:GetCoalition()) end end @@ -1872,6 +1885,70 @@ function WAREHOUSE:GetNumberOfAssets(Descriptor, DescriptorValue) end +--- Get coordinate of warehouse static. +-- @param #WAREHOUSE self +-- @return Core.Point#COORDINATE The coordinate of the warehouse. +function WAREHOUSE:GetCoordinate() + return self.warehouse:GetCoordinate() +end + +--- Get coalition side of warehouse static. +-- @param #WAREHOUSE self +-- @return #number Coalition side, i.e. number of @{DCS#coalition.side}. +function WAREHOUSE:GetCoalition() + return self.warehouse:GetCoalition() +end + +--- Get coalition name of warehouse static. +-- @param #WAREHOUSE self +-- @return #number Coalition side, i.e. number of @{DCS#coalition.side}. +function WAREHOUSE:GetCoalitionName() + return self.warehouse:GetCoalitionName() +end + +--- Get country id of warehouse static. +-- @param #WAREHOUSE self +-- @return #number Country id, i.e. number of @{DCS#country.id}. +function WAREHOUSE:GetCountry() + return self.warehouse:GetCountry() +end + +--- Get country name of warehouse static. +-- @param #WAREHOUSE self +-- @return #number Country id, i.e. number of @{DCS#coalition.side}. +function WAREHOUSE:GetCountryName() + return self.warehouse:GetCountryName() +end + +--- Get airbase associated to the warehouse. +-- @param #WAREHOUSE self +-- @return Wrapper.Airbase#AIRBASE Airbase object or nil if warehouse has no airbase connection. +function WAREHOUSE:GetAirbase() + return self.airbase +end + +--- Get name airbase associated to the warehouse. +-- @param #WAREHOUSE self +-- @return #string name of the airbase assosicated to the warehouse or "none" if the airbase has not airbase connection currently. +function WAREHOUSE:GetAirbaseName() + local name="none" + if self.airbase then + name=self.airbase:GetName() + end + return name +end + +--- Get category of airbase associated to the warehouse. +-- @param #WAREHOUSE self +-- @return #number Category of airbase or -1 if warehouse has (currently) no airbase. +function WAREHOUSE:GetAirbaseCategory() + local category=-1 + if self.airbase then + category=self.airbase:GetDesc().category + end + return category +end + --- Get assignment of a request. -- @param #WAREHOUSE self -- @param #WAREHOUSE.Pendingitem request The request from which the assignment is extracted. @@ -1880,6 +1957,48 @@ function WAREHOUSE:GetAssignment(request) return tostring(request.assignment) end +--- Get warehouse unique ID from static warehouse object. This is the ID under which you find the @{#WAREHOUSE} object in the global data base. +-- @param #WAREHOUSE self +-- @param #string staticname Name of the warehouse static object. +-- @return #number Warehouse unique ID. +function WAREHOUSE:GetWarehouseID(staticname) + local warehouse=STATIC:FindByName(staticname, true) + local uid=tonumber(warehouse:GetID()) + return uid +end + +--- Find a warehouse in the global warehouse data base. +-- @param #WAREHOUSE self +-- @param #number uid The unique ID of the warehouse. +-- @return #WAREHOUSE The warehouse object or nil if no warehouse exists. +function WAREHOUSE:FindWarehouseInDB(uid) + return WAREHOUSE.db.Warehouses[uid] +end + +--- Find an asset in the the global warehouse data base. Parameter is the MOOSE group object. +-- Note that the group name must contain they "AID" keyword. +-- @param #WAREHOUSE self +-- @param Wrapper.Group#GROUP group The group from which it is assumed that it has a registered asset. +-- @return #WAREHOUSE.Assetitem The asset from the data base or nil if it could not be found. +function WAREHOUSE:FindAssetInDB(group) + + -- Get unique ids from group name. + local wid,aid,rid=self:_GetIDsFromGroup(group) + + if aid~=nil then + + local asset=WAREHOUSE.db.Assets[aid] + self:E({asset=asset}) + if asset==nil then + self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName()), 0) + end + return asset + end + + self:_ErrorMessage(string.format("ERROR: Group %s does not contain an asset ID in its name!", group:GetName()), 0) + return nil +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1893,21 +2012,19 @@ function WAREHOUSE:onafterStart(From, Event, To) -- Short info. local text=string.format("Starting warehouse %s alias %s:\n",self.warehouse:GetName(), self.alias) - text=text..string.format("Coaliton = %d\n", self.coalition) - text=text..string.format("Country = %d\n", self.country) - text=text..string.format("Airbase = %s (%s)\n", tostring(self.airbase:GetName()), tostring(self.category)) + text=text..string.format("Coaliton = %s\n", self:GetCoalitionName()) + text=text..string.format("Country = %s\n", self:GetCountryName()) + text=text..string.format("Airbase = %s (category=%d)\n", self:GetAirbaseName(), self:GetAirbaseCategory()) env.info(text) -- Save self in static object. Easier to retrieve later. self.warehouse:SetState(self.warehouse, "WAREHOUSE", self) -- Set airbase name and category. - if self.airbase and self.airbase:GetCoalition()==self.coalition then + if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then self.airbasename=self.airbase:GetName() - self.category=self.airbase:GetDesc().category else self.airbasename=nil - self.category=-1 -- The -1 indicates that we dont have an airbase at this warehouse. end -- THIS! caused aircraft to be spawned and started but they would never begin their route! @@ -2026,9 +2143,6 @@ end function WAREHOUSE:onafterStatus(From, Event, To) self:I(self.wid..string.format("Checking status of warehouse %s. Current FSM state %s. Global warehouse assets = %d.", self.alias, self:GetState(), #WAREHOUSE.db.Assets)) - -- Update coordinate in case we have a "moving" warehouse (e.g. on a carrier). - self.coordinate=self.warehouse:GetCoordinate() - -- Check if any pending jobs are done and can be deleted from the self:_JobDone() @@ -2282,7 +2396,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu --------------------------- -- Get the original warehouse this group belonged to. - local warehouse=self:_FindWarehouseInDB(wid) + local warehouse=self:FindWarehouseInDB(wid) if warehouse then local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) if request then @@ -2305,7 +2419,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu end -- Get the asset from the global DB. - local asset=self:_FindAssetInDB(group) + local asset=self:FindAssetInDB(group) -- Note the group is only added once, i.e. the ngroups parameter is ignored here. -- This is because usually these request comes from an asset that has been transfered from another warehouse and hence should only be added once. @@ -2348,38 +2462,6 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu self:__Status(-1) end - ---- Find an asset in the the global warehouse db. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group from which it is assumed that it has a registered asset. --- @return #WAREHOUSE.Assetitem The asset from the data base or nil if it could not be found. -function WAREHOUSE:_FindAssetInDB(group) - - -- Get unique ids from group name. - local wid,aid,rid=self:_GetIDsFromGroup(group) - - if aid~=nil then - - local asset=WAREHOUSE.db.Assets[aid] - self:E({asset=asset}) - if asset==nil then - self:_ErrorMessage(string.format("ERROR: Asset for group %s not found in the data base!", group:GetName()), 0) - end - return asset - end - - self:_ErrorMessage(string.format("ERROR: Group %s does not contain an asset ID in its name!", group:GetName()), 0) - return nil -end - ---- Find a warehouse in the global warehouse data base. --- @param #WAREHOUSE self --- @param #number uid The unique ID of the warehouse. --- @return #WAREHOUSE The warehouse object or nil if no warehouse exists. -function WAREHOUSE:_FindWarehouseInDB(uid) - return WAREHOUSE.db.Warehouses[uid] -end - --- Register new asset in globase warehouse data base. -- @param #WAREHOUSE self -- @param Wrapper.Group#GROUP group The group that will be added to the warehouse stock. @@ -2484,7 +2566,7 @@ end --- Asset item characteristics. -- @param #WAREHOUSE self --- @param #WAREHOUSE.Assetitem asset +-- @param #WAREHOUSE.Assetitem asset The asset for which info in printed in trace mode. function WAREHOUSE:_AssetItemInfo(asset) -- Info about asset: local text=string.format("\nNew asset with id=%d for warehouse %s:\n", asset.uid, self.alias) @@ -2622,8 +2704,8 @@ function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor transporttype=TransportType, ntransport=nTransport, assignment=tostring(Assignment), - airbase=warehouse.airbase, - category=warehouse.category, + airbase=warehouse:GetAirbase(), + category=warehouse:GetAirbaseCategory(), ndelivered=0, ntransporthome=0, assets={}, @@ -2654,7 +2736,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) self:T3({warehouse=self.alias, request=Request}) -- Distance from warehouse to requesting warehouse. - local distance=self.coordinate:Get2DDistance(Request.warehouse.coordinate) + local distance=self:GetCoordinate():Get2DDistance(Request.warehouse:GetCoordinate()) -- Shortcut to cargoassets. local _assets=Request.cargoassets @@ -3195,7 +3277,7 @@ function WAREHOUSE:onafterArrived(From, Event, To, group) -- Route mobile ground group to the warehouse. Group has 60 seconds to get there or it is despawned and added as asset to the new warehouse regardless. if group:IsGround() and group:GetSpeedMax()>1 then - group:RouteGroundTo(warehouse.coordinate, group:GetSpeedMax()*0.3, "Off Road") + group:RouteGroundTo(warehouse:GetCoordinate(), group:GetSpeedMax()*0.3, "Off Road") end -- Increase number of cargo delivered and transports home. @@ -3248,7 +3330,7 @@ function WAREHOUSE:onafterDelivered(From, Event, To, request) self:_InfoMessage(text, 5) -- Make some noise :) - self:_Fireworks(request.warehouse.coordinate) + self:_Fireworks(request.warehouse:GetCoordinate()) -- Set delivered status for this request uid. self.delivered[request.uid]=true @@ -3318,7 +3400,7 @@ function WAREHOUSE:onafterAttacked(From, Event, To, Coalition, Country) -- Debug smoke. if self.Debug then - self.coordinate:SmokeOrange() + self:GetCoordinate():SmokeOrange() end -- Spawn all ground units in the spawnzone? @@ -3354,7 +3436,7 @@ function WAREHOUSE:onafterDefeated(From, Event, To) -- Debug smoke. if self.Debug then - self.coordinate:SmokeGreen() + self:GetCoordinate():SmokeGreen() end -- Auto defence: put assets back into stock. @@ -3368,7 +3450,7 @@ function WAREHOUSE:onafterDefeated(From, Event, To) -- Get max speed of group and route it back slowly to the warehouse. local speed=group:GetSpeedMax() if group:IsGround() and speed>1 then - group:RouteGroundTo(self.coordinate, speed*0.3) + group:RouteGroundTo(self:GetCoordinate(), speed*0.3) end -- Add asset group back to stock after 60 seconds. @@ -3392,16 +3474,12 @@ end function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) -- Message. - local text=string.format("Warehouse %s: We were captured by enemy coalition (%d)!", self.alias, Coalition) + local text=string.format("Warehouse %s: We were captured by enemy coalition (ID=%d)!", self.alias, Coalition) self:_InfoMessage(text) -- Respawn warehouse with new coalition/country. self.warehouse:ReSpawn(Country) - -- Set new country and coalition - self.coalition=Coalition - self.country=Country - -- Delete all waiting requests because they are not valid any more self.queue=nil self.queue={} @@ -3410,22 +3488,20 @@ function WAREHOUSE:onafterCaptured(From, Event, To, Coalition, Country) local airbase=AIRBASE:FindByName(self.airbasename) local airbasecoaltion=airbase:GetCoalition() - if self.coalition==airbasecoaltion then + if Coalition==airbasecoaltion then -- Airbase already owned by the coalition that captured the warehouse. Airbase can be used by this warehouse. self.airbase=airbase - self.category=airbase:GetDesc().category else -- Airbase is owned by other coalition. So this warehouse does not have an airbase unil it is captured. self.airbase=nil - self.category=-1 end -- Debug smoke. if self.Debug then if Coalition==coalition.side.RED then - self.coordinate:SmokeRed() + self:GetCoordinate():SmokeRed() elseif Coalition==coalition.side.BLUE then - self.coordinate:SmokeBlue() + self:GetCoordinate():SmokeBlue() end end @@ -3454,7 +3530,6 @@ function WAREHOUSE:onafterAirbaseCaptured(From, Event, To, Coalition) -- Set airbase to nil and category to no airbase. self.airbase=nil - self.category=-1 -- -1 indicates no airbase. end --- On after "AirbaseRecaptured" event. Airbase of warehouse has been re-captured from other coalition. @@ -3462,7 +3537,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param DCS#coalition.side Coalition which captured the warehouse. +-- @param DCS#coalition.side Coalition Coalition side which originally captured the warehouse. function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) -- Message. @@ -3471,7 +3546,6 @@ function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) -- Set airbase and category. self.airbase=AIRBASE:FindByName(self.airbasename) - self.category=self.airbase:GetDesc().category -- Debug smoke. if self.Debug then @@ -3713,7 +3787,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol -- Get airbase ID and category. local AirbaseID = self.airbase:GetID() - local AirbaseCategory = self.category + local AirbaseCategory = self:GetAirbaseCategory() -- Check enough parking spots. if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then @@ -3809,8 +3883,8 @@ function WAREHOUSE:_SpawnAssetPrepareTemplate(asset, alias) template.name=alias -- Set current(!) coalition and country. - template.CoalitionID=self.coalition - template.CountryID=self.country + template.CoalitionID=self:GetCoalition() + template.CountryID=self:GetCountry() -- Nillify the group ID. template.groupId=nil @@ -4423,19 +4497,19 @@ function WAREHOUSE:_OnEventBaseCaptured(EventData) local NewCoalitionAirbase=airbase:GetCoalition() -- Debug info - self:T(self.wid..string.format("Airbase of warehouse %s (coalition = %d) was captured! New owner coalition = %d.",self.alias, self.coalition, NewCoalitionAirbase)) + self:T(self.wid..string.format("Airbase of warehouse %s (coalition ID=%d) was captured! New owner coalition ID=%d.",self.alias, self:GetCoalition(), NewCoalitionAirbase)) -- So what can happen? -- Warehouse is blue, airbase is blue and belongs to warehouse and red captures it ==> self.airbase=nil -- Warehouse is blue, airbase is blue self.airbase is nil and blue (re-)captures it ==> self.airbase=Event.Place if self.airbase==nil then -- New coalition is the same as of the warehouse ==> warehouse previously lost this airbase and now it was re-captured. - if NewCoalitionAirbase == self.coalition then + if NewCoalitionAirbase == self:GetCoalition() then self:AirbaseRecaptured(NewCoalitionAirbase) end else -- Captured airbase belongs to this warehouse but was captured by other coaltion. - if NewCoalitionAirbase ~= self.coalition then + if NewCoalitionAirbase ~= self:GetCoalition() then self:AirbaseCaptured(NewCoalitionAirbase) end end @@ -4506,8 +4580,8 @@ function WAREHOUSE:_CheckConquered() -- Figure out the new coalition if any. -- Condition is that only units of one coalition are within the zone. - local newcoalition=self.coalition - local newcountry=self.country + local newcoalition=self:GetCoalition() + local newcountry=self:GetCountry() if Nblue>0 and Nred==0 and Nneutral==0 then -- Only blue units in zone ==> Zone goes to blue. newcoalition=coalition.side.BLUE @@ -4523,14 +4597,14 @@ function WAREHOUSE:_CheckConquered() end -- Coalition has changed ==> warehouse was captured! This should be before the attack check. - if self:IsAttacked() and newcoalition ~= self.coalition then + if self:IsAttacked() and newcoalition ~= self:GetCoalition() then self:Captured(newcoalition, newcountry) return end -- Before a warehouse can be captured, it has to be attacked. -- That is, even if only enemy units are present it is not immediately captured in order to spawn all ground assets for defence. - if self.coalition==coalition.side.BLUE then + if self:GetCoalition()==coalition.side.BLUE then -- Blue warehouse is running and we have red units in the zone. if self:IsRunning() and Nred>0 then self:Attacked(coalition.side.RED, CountryRed) @@ -4539,7 +4613,7 @@ function WAREHOUSE:_CheckConquered() if self:IsAttacked() and Nred==0 then self:Defeated() end - elseif self.coalition==coalition.side.RED then + elseif self:GetCoalition()==coalition.side.RED then -- Red Warehouse is running and we have blue units in the zone. if self:IsRunning() and Nblue>0 then self:Attacked(coalition.side.BLUE, CountryBlue) @@ -4548,7 +4622,7 @@ function WAREHOUSE:_CheckConquered() if self:IsAttacked() and Nblue==0 then self:Defeated() end - elseif self.coalition==coalition.side.NEUTRAL then + elseif self:GetCoalition()==coalition.side.NEUTRAL then -- Neutrals dont attack! end @@ -4566,17 +4640,15 @@ function WAREHOUSE:_CheckAirbaseOwner() if self.airbase then -- Warehouse has lost its airbase. - if self.coalition~=airbasecurrentcoalition then + if self:GetCoalition()~=airbasecurrentcoalition then self.airbase=nil - self.category=-1 end else -- Warehouse has re-captured the airbase. - if self.coalition==airbasecurrentcoalition then + if self:GetCoalition()==airbasecurrentcoalition then self.airbase=airbase - self.category=airbase:GetDesc().category end end @@ -4611,8 +4683,8 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) end -- Request from enemy coalition? - if self.coalition~=request.warehouse.coalition then - self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coaltion! Own coalition %d != %d of requesting warehouse.", self.coalition, request.warehouse.coalition)) + if self:GetCoalition()~=request.warehouse:GetCoalition() then + self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is of wrong coaltion! Own coalition %s != %s of requesting warehouse.", self:GetCoalitionName(), request.warehouse:GetCoalitionName())) valid=false end @@ -4681,6 +4753,9 @@ function WAREHOUSE:_CheckRequestValid(request) -- Assume everything is okay. local valid=true + -- Category of the requesting warehouse airbase. + local requestcategory=request.warehouse:GetAirbaseCategory() + if request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then ------------------------------------------- -- Case where the units go my themselves -- @@ -4691,7 +4766,7 @@ function WAREHOUSE:_CheckRequestValid(request) if asset_plane then -- No airplane to or from FARPS. - if request.category==Airbase.Category.HELIPAD or self.category==Airbase.Category.HELIPAD then + if requestcategory==Airbase.Category.HELIPAD or self:GetAirbaseCategory()==Airbase.Category.HELIPAD then self:E("ERROR: Incorrect request. Asset airplane requested but warehouse or requestor is HELIPAD/FARP!") valid=false end @@ -4703,7 +4778,7 @@ function WAREHOUSE:_CheckRequestValid(request) -- Helos need a FARP or AIRBASE or SHIP for spawning. Also at the the receiving warehouse. So even if they could go there they "cannot" be spawned again. -- Unless I allow spawning of helos in the the spawn zone. But one should place at least a FARP there. - if self.category==-1 or request.category==-1 then + if self:GetAirbaseCategory()==-1 or requestcategory==-1 then self:E("ERROR: Incorrect request. Helos need a AIRBASE/HELIPAD/SHIP as home/destination base!") valid=false end @@ -4751,7 +4826,7 @@ function WAREHOUSE:_CheckRequestValid(request) -- No ground assets directly to or from ships. -- TODO: May needs refinement if warehouse is on land and requestor is ship in harbour?! - if (request.category==Airbase.Category.SHIP or self.category==Airbase.Category.SHIP) then + if (requestcategory==Airbase.Category.SHIP or self:GetAirbaseCategory()==Airbase.Category.SHIP) then self:E("ERROR: Incorrect request. Ground asset requested but warehouse or requestor is SHIP!") valid=false end @@ -4804,7 +4879,7 @@ function WAREHOUSE:_CheckRequestValid(request) if request.transporttype==WAREHOUSE.TransportType.AIRPLANE then -- Airplanes only to AND from airdromes. - if self.category~=Airbase.Category.AIRDROME or request.category~=Airbase.Category.AIRDROME then + if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME or requestcategory~=Airbase.Category.AIRDROME then self:E("ERROR: Incorrect request. Warehouse or requestor does not have an airdrome. No transport by plane possible!") valid=false end @@ -4816,7 +4891,7 @@ function WAREHOUSE:_CheckRequestValid(request) -- Transport by ground units. -- No transport to or from ships - if self.category==Airbase.Category.SHIP or request.category==Airbase.Category.SHIP then + if self:GetAirbaseCategory()==Airbase.Category.SHIP or requestcategory==Airbase.Category.SHIP then self:E("ERROR: Incorrect request. Warehouse or requestor is SHIP. No transport by APC possible!") valid=false end @@ -4831,7 +4906,7 @@ function WAREHOUSE:_CheckRequestValid(request) elseif request.transporttype==WAREHOUSE.TransportType.HELICOPTER then -- Transport by helicopters ==> need airbase for spawning but not for delivering to the spawn zone of the receiver. - if self.category==-1 then + if self:GetAirbaseCategory()==-1 then self:E("ERROR: Incorrect request. Warehouse has no airbase. Transport by helicopter not possible!") valid=false end @@ -5933,13 +6008,7 @@ function WAREHOUSE:_PrintQueue(queue, name) for i,qitem in ipairs(queue) do local qitem=qitem --#WAREHOUSE.Pendingitem - - -- Set airbase: - local airbasename="none" - if qitem.airbase then - airbasename=qitem.airbase:GetName() - end - + local uid=qitem.uid local prio=qitem.prio local clock="N/A" @@ -5948,7 +6017,8 @@ function WAREHOUSE:_PrintQueue(queue, name) end local assignment=tostring(qitem.assignment) local requestor=qitem.warehouse.alias - local requestorAirbaseCat=qitem.category + local airbasename=qitem.warehouse:GetAirbaseName() + local requestorAirbaseCat=qitem.warehouse:GetAirbaseCategory() local assetdesc=qitem.assetdesc local assetdescval=qitem.assetdescval local nasset=tostring(qitem.nasset) @@ -5986,20 +6056,13 @@ end --- Display status of warehouse. -- @param #WAREHOUSE self -function WAREHOUSE:_DisplayStatus() - - -- Set airbase name. - local airbasename="none" - if self.airbase then - airbasename=self.airbase:GetName() - end - +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("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) + text=text..string.format("Coalition name = %d\n", self:GetCoalitionName()) + text=text..string.format("Country name = %d\n", self:GetCountryName()) + text=text..string.format("Airbase name = %s\n", self:GetAirbaseName()) text=text..string.format("Queued requests = %d\n", #self.queue) text=text..string.format("Pending requests = %d\n", #self.pending) text=text..string.format("------------------------------------------------------\n") @@ -6016,13 +6079,6 @@ function WAREHOUSE:_GetStockAssetsText(messagetoall) -- Get assets in stock. local _data=self:GetStockInfo(self.stock) - --[[ - local function _sort(a,b) - return a[1]0 then - MESSAGE:New(text, duration):ToCoalitionIf(self.coalition, self.Debug or self.Report) + MESSAGE:New(text, duration):ToCoalitionIf(self:GetCoalition(), self.Debug or self.Report) end self:I(self.wid..text) end diff --git a/Moose Development/Moose/Wrapper/Identifiable.lua b/Moose Development/Moose/Wrapper/Identifiable.lua index 0f6e14f06..13a5b3234 100644 --- a/Moose Development/Moose/Wrapper/Identifiable.lua +++ b/Moose Development/Moose/Wrapper/Identifiable.lua @@ -206,8 +206,19 @@ function IDENTIFIABLE:GetCountry() self:F( self.ClassName .. " " .. self.IdentifiableName .. " not found!" ) return nil end - +--- Returns country name of the Identifiable. +-- @param #IDENTIFIABLE self +-- @return #string Name of the country. +function IDENTIFIABLE:GetCountryName() + self:F2( self.IdentifiableName ) + local countryid=self:GetCountry() + for name,id in pairs(country.id) do + if countryid==id then + return name + end + end +end --- Returns Identifiable descriptor. Descriptor type depends on Identifiable category. -- @param #IDENTIFIABLE self From 88734fa9fcc94b4b0ae343bc4f72ec9afb3b0e1f Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sat, 15 Sep 2018 00:31:55 +0200 Subject: [PATCH 63/73] Warehouse v0.4.5 Fixed overlapping aircraft on parking spots. Other fixes. --- .../Moose/Functional/Warehouse.lua | 74 ++++++++++--------- Moose Development/Moose/Wrapper/Client.lua | 4 +- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 9b37cea88..eaa7e9ff9 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -985,13 +985,12 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.4w" +WAREHOUSE.version="0.4.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Check overlapping aircraft sometimes. -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Test capturing a neutral warehouse. -- TODO: Make more examples: ARTY, CAP, ... @@ -999,6 +998,7 @@ WAREHOUSE.version="0.4.4w" -- TODO: Handle the case when units of a group die during the transfer. -- TODO: Added habours as interface for transport to from warehouses? -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua! +-- DONE: Check overlapping aircraft sometimes. -- DONE: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. Should be done now! -- DONE: Add transport units from dispatchers back to warehouse stock once they completed their mission. -- DONE: Write documentation. @@ -3355,10 +3355,6 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) -- Debug info. for _,_group in pairs(groupset:GetSetObjects()) do local group=_group --Wrapper.Group#GROUP - - --local text=string.format("Group name = %s, IsAlive=%s.", tostring(group:GetName()), tostring(group:IsAlive())) - --env.info(text) - if self.Debug then group:FlareGreen() end @@ -3378,11 +3374,10 @@ function WAREHOUSE:onafterSelfRequest(From, Event, To, groupset, request) end end + -- Add request to defenders. table.insert(self.defending, request) end - -- Remove pending request. - --self:_DeleteQueueItem(request, self.pending) end --- On after "Attacked" event. Warehouse is under attack by an another coalition. @@ -5412,7 +5407,7 @@ end function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Init default - local scanradius=50 + local scanradius=100 local scanunits=true local scanstatics=true local scanscenery=false @@ -5420,7 +5415,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Function calculating the overlap of two (square) objects. local function _overlap(l1,l2,dist) - local safedist=(l1/2+l2/2)*1.1 + local safedist=(l1/2+l2/2)*1.1 -- 10% safety margine added to safe distance! local safe = (dist > safedist) self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe))) return safe @@ -5432,18 +5427,15 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- List of obstacles. local obstacles={} - -- Loop over all parking spots and get the obstacles. + -- Loop over all parking spots and get the currently present obstacles. -- How long does this take on very large airbases, i.e. those with hundereds of parking spots? Seems to be okay! for _,parkingspot in pairs(parkingdata) do -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID - - -- Obstacles at or around this parking spot. - obstacles[_termid]={} - -- Scan a radius of 50 meters around the spot. + -- Scan a radius of 100 meters around the spot. local _,_,_,_units,_statics,_sceneries=_spot:ScanObjects(scanradius, scanunits, scanstatics, scanscenery) -- Check all units. @@ -5452,7 +5444,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local _coord=unit:GetCoordinate() local _size=self:_GetObjectSize(unit:GetDCSObject()) local _name=unit:GetName() - table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="unit"}) + table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="unit"}) end -- Check all statics. @@ -5461,7 +5453,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local _coord=COORDINATE:NewFromVec3(_vec3) local _name=static:getName() local _size=self:_GetObjectSize(static) - table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="static"}) + table.insert(obstacles, {coord=_coord, size=_size, name=_name, type="static"}) end -- Check all scenery. @@ -5470,21 +5462,23 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) local _coord=COORDINATE:NewFromVec3(_vec3) local _name=scenery:getTypeName() local _size=self:_GetObjectSize(scenery) - table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="scenery"}) + table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="scenery"}) end - -- TODO Clients? Unoccupied client aircraft are also important! Are they already included in scanned units maybe? --[[ + -- TODO Clients? Unoccupied client aircraft are also important! Are they already included in scanned units maybe? local clients=_DATABASE.CLIENTS for _,_client in pairs(clients) do local client=_client --Wrapper.Client#CLIENT - local unit=client:GetClientGroupUnit() + env.info(string.format("FF Client name %s", client:GetName())) + local unit=UNIT:FindByName(client:GetName()) + --local unit=client:GetClientGroupUnit() local _coord=unit:GetCoordinate() local _name=unit:GetName() local _size=self:_GetObjectSize(client:GetClientGroupDCSUnit()) - table.insert(obstacles[_termid],{coord=_coord, size=_size, name=_name, type="client"}) - end - ]] + table.insert(obstacles,{coord=_coord, size=_size, name=_name, type="client"}) + end + ]] end -- Parking data for all assets. @@ -5492,9 +5486,9 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Loop over all assets that need a parking psot. for _,asset in pairs(assets) do - local _asset=asset --#WAREHOUSE.Assetitem + -- Get terminal type of this asset local terminaltype=self:_GetTerminal(asset.attribute) -- Asset specific parking. @@ -5505,20 +5499,23 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Loop over all parking spots. local gotit=false - for _,parkingspot in pairs(parkingdata) do + for _,_parkingspot in pairs(parkingdata) do + local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot -- Check correct terminal type for asset. We don't want helos in shelters etc. - if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then + if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then -- Coordinate of the parking spot. local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID local _toac=parkingspot.TOAC + + --env.info(string.format("FF asset=%s (id=%d): needs terminal type=%d, id=%d, #obstacles=%d", _asset.templatename, _asset.uid, terminaltype, _termid, #obstacles)) -- Loop over all obstacles. local free=true local problem=nil - for _,obstacle in pairs(obstacles[_termid]) do + for _,obstacle in pairs(obstacles) do -- Check if aircraft overlaps with any obstacle. local dist=_spot:Get2DDistance(obstacle.coord) @@ -5526,14 +5523,18 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Spot is blocked. if not safe then + --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", _asset.templatename, _asset.uid, _termid, dist)) free=false problem=obstacle problem.dist=dist break + else + --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", _asset.templatename, _asset.uid, _termid, dist)) end end + -- Check if spot is free if free then -- Add parkingspot for this asset unit. @@ -5543,23 +5544,27 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Add the unit as obstacle so that this spot will not be available for the next unit. -- TODO Alternatively, I could remove this parking spot from the table, right? - table.insert(obstacles[_termid], {coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"}) + table.insert(obstacles, {coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"}) gotit=true break + else + + -- Debug output for occupied spots. self:T(self.wid..string.format("Parking spot #%d is occupied or not big enough!", _termid)) if self.Debug then local coord=problem.coord --Core.Point#COORDINATE local text=string.format("Obstacle blocking spot #%d is %s type %s with size=%.1f m and distance=%.1f m.", _termid, problem.name, problem.type, problem.size, problem.dist) coord:MarkToAll(string.format(text)) end + end end -- check terminal type end -- loop over parking spots - + -- No parking spot for at least one asset :( if not gotit then self:T(self.wid..string.format("WARNING: No free parking spot for asset id=%d",_asset.uid)) return nil @@ -5916,7 +5921,10 @@ end --- Size of the bounding box of a DCS object derived from the DCS descriptor table. If boundinb box is nil, a size of zero is returned. -- @param #WAREHOUSE self -- @param DCS#Object DCSobject The DCS object for which the size is needed. --- @return #number Max size of object in meters. +-- @return #number Max size of object in meters (length (x) or width (z) components not including height (y)). +-- @return #number Length (x component) of size. +-- @return #number Height (y component) of size. +-- @return #number Width (z component) of size. function WAREHOUSE:_GetObjectSize(DCSobject) local DCSdesc=DCSobject:getDesc() if DCSdesc.box then @@ -6060,9 +6068,9 @@ 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("Coalition name = %d\n", self:GetCoalitionName()) - text=text..string.format("Country name = %d\n", self:GetCountryName()) - text=text..string.format("Airbase name = %s\n", self:GetAirbaseName()) + text=text..string.format("Coalition name = %s\n", self:GetCoalitionName()) + text=text..string.format("Country name = %s\n", self:GetCountryName()) + text=text..string.format("Airbase name = %s (category=%d)\n", self:GetAirbaseName(), self:GetAirbaseCategory()) text=text..string.format("Queued requests = %d\n", #self.queue) text=text..string.format("Pending requests = %d\n", #self.pending) text=text..string.format("------------------------------------------------------\n") diff --git a/Moose Development/Moose/Wrapper/Client.lua b/Moose Development/Moose/Wrapper/Client.lua index 7df3c0586..a4178538e 100644 --- a/Moose Development/Moose/Wrapper/Client.lua +++ b/Moose Development/Moose/Wrapper/Client.lua @@ -376,8 +376,8 @@ end -- @param #CLIENT self -- @return Wrapper.Unit#UNIT function CLIENT:GetClientGroupUnit() - self:F2() - + self:F2() + local ClientDCSUnit = Unit.getByName( self.ClientName ) self:T( self.ClientDCSUnit ) From e16d6c0ca44ce74a07a0e2d62a5ba5612bdf3bc3 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sat, 15 Sep 2018 20:26:08 +0200 Subject: [PATCH 64/73] Warehouse v0.4.6 Little adjustments. Controllable: Fixed bombing task. --- Moose Development/Moose/Core/Point.lua | 10 ++-- .../Moose/Functional/Warehouse.lua | 5 +- .../Moose/Wrapper/Controllable.lua | 52 ++++++++++++++----- 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index c899a48c6..b5d173644 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -964,7 +964,7 @@ do -- COORDINATE -- @param DCS#Speed Speed Airspeed in km/h. Default is 500 km/h. -- @param #boolean SpeedLocked true means the speed is locked. -- @param Wrapper.Airbase#AIRBASE airbase The airbase for takeoff and landing points. - -- @param #table DCSTasks A table of DCS#Task items which are executed at the waypoint. + -- @param #table DCSTasks A table of @{DCS#Task} items which are executed at the waypoint. -- @param #string description A text description of the waypoint, which will be shown on the F10 map. -- @return #table The route point. function COORDINATE:WaypointAir( AltType, Type, Action, Speed, SpeedLocked, airbase, DCSTasks, description ) @@ -1028,7 +1028,7 @@ do -- COORDINATE RoutePoint.task.params = {} RoutePoint.task.params.tasks = DCSTasks or {} - self:E({RoutePoint=RoutePoint}) + self:T({RoutePoint=RoutePoint}) return RoutePoint end @@ -1037,9 +1037,11 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. -- @param DCS#Speed Speed Airspeed in km/h. + -- @param #table DCSTasks (Optional) A table of @{DCS#Task} items which are executed at the waypoint. + -- @param #string description (Optional) A text description of the waypoint, which will be shown on the F10 map. -- @return #table The route point. - function COORDINATE:WaypointAirTurningPoint( AltType, Speed ) - return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed ) + function COORDINATE:WaypointAirTurningPoint( AltType, Speed, DCSTasks, description ) + return self:WaypointAir( AltType, COORDINATE.WaypointType.TurningPoint, COORDINATE.WaypointAction.TurningPoint, Speed, true, nil, DCSTasks, description ) end diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index eaa7e9ff9..73ba2f6c6 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -985,7 +985,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.5" +WAREHOUSE.version="0.4.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -5415,7 +5415,7 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Function calculating the overlap of two (square) objects. local function _overlap(l1,l2,dist) - local safedist=(l1/2+l2/2)*1.1 -- 10% safety margine added to safe distance! + local safedist=(l1/2+l2/2)*1.05 -- 5% safety margine added to safe distance! local safe = (dist > safedist) self:T3(string.format("l1=%.1f l2=%.1f s=%.1f d=%.1f ==> safe=%s", l1,l2,safedist,dist,tostring(safe))) return safe @@ -5543,7 +5543,6 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) self:T(self.wid..string.format("Parking spot #%d is free for asset id=%d!", _termid, _asset.uid)) -- Add the unit as obstacle so that this spot will not be available for the next unit. - -- TODO Alternatively, I could remove this parking spot from the table, right? table.insert(obstacles, {coord=_spot, size=_asset.size, name=_asset.templatename, type="asset"}) gotit=true diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 92aed3843..256294fac 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -726,28 +726,56 @@ end -- @param DCS#Azimuth Direction (optional) Desired ingress direction from the target to the attacking aircraft. Controllable/aircraft will make its attacks from the direction. Of course if there is no way to attack from the direction due the terrain controllable/aircraft will choose another direction. -- @param #number Altitude (optional) The altitude from where to attack. -- @param #number WeaponType (optional) The WeaponType. +-- @param #boolean Divebomb (optional) Perform dive bombing. Default false. -- @return DCS#Task The DCS task structure. -function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType ) - self:F2( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType } ) +function CONTROLLABLE:TaskBombing( Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb ) + self:E( { self.ControllableName, Vec2, GroupAttack, WeaponExpend, AttackQty, Direction, Altitude, WeaponType, Divebomb } ) + + local _groupattack=false + if GroupAttack then + _groupattack=GroupAttack + end + + local _direction=0 + local _directionenabled=false + if Direction then + _direction=math.rad(Direction) + _directionenabled=true + end + + local _altitude=5000 + local _altitudeenabled=false + if Altitude then + _altitude=Altitude + _altitudeenabled=true + end + + local _attacktype=nil + if Divebomb then + _attacktype="Dive" + end + local DCSTask DCSTask = { id = 'Bombing', params = { - point = Vec2, - groupAttack = GroupAttack or false, + x = Vec2.x, + y = Vec2.y, + groupAttack = _groupattack, expend = WeaponExpend or "Auto", - attackQtyLimit = AttackQty and true or false, - attackQty = AttackQty, - directionEnabled = Direction and true or false, - direction = Direction, - altitudeEnabled = Altitude and true or false, - altitude = Altitude or 30, + attackQtyLimit = false, --AttackQty and true or false, + attackQty = AttackQty or 1, + directionEnabled = _directionenabled, + direction = _direction, + altitudeEnabled = _altitudeenabled, + altitude = _altitude, weaponType = WeaponType, + --attackType=_attacktype, }, - }, + } - self:T3( { DCSTask } ) + self:E( { TaskBombing=DCSTask } ) return DCSTask end From 8f08fd87befef281be54079441b81ed0135ff74b Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 16 Sep 2018 01:36:41 +0200 Subject: [PATCH 65/73] Warehouse v0.4.7 Added EWR as attribute. Fixed bug in shipping lanes. Added airbase check in arrived event. Removed starting point from shipping lane since it is in the port zone anyway. --- .../Moose/Functional/Warehouse.lua | 72 ++++++------------- 1 file changed, 23 insertions(+), 49 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 73ba2f6c6..39abc47c4 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -425,7 +425,7 @@ -- -- This section shows some examples how the WAREHOUSE class is used in practice. This is one of the best ways to explain things, in my opinion. -- --- But first, let me introduce a convenient way to define several warehouses in a table. This is absolutely *not* necessary but quite handy if you have +-- But first, let me introduce a convenient way to define several warehouses in a table. This is absolutely *not necessary* but quite handy if you have -- multiple WAREHOUSE objects in your mission. -- -- ## Example 0: Setting up a Warehouse Array @@ -899,11 +899,12 @@ WAREHOUSE.Descriptor = { -- @field #string AIR_UAV Unpiloted Aerial Vehicle, e.g. drones. -- @field #string AIR_OTHER Any airborne unit that does not fall into any other airborne category. -- @field #string GROUND_APC Infantry carriers, in particular Amoured Personell Carrier. This can be used to transport other assets. --- @field #string GROUND_TRUCK Unarmed ground vehicles. +-- @field #string GROUND_TRUCK Unarmed ground vehicles, which has the DCS "Truck" attribute. -- @field #string GROUND_INFANTRY Ground infantry assets. -- @field #string GROUND_ARTILLERY Artillery assets. -- @field #string GROUND_TANK Tanks (modern or old). -- @field #string GROUND_TRAIN Trains. Not that trains are **not** yet properly implemented in DCS and cannot be used currently. +-- @field #string GROUND_EWR Early Warning Radar. -- @field #string GROUND_AAA Anti-Aircraft Artillery. -- @field #string GROUND_SAM Surface-to-Air Missile system or components. -- @field #string GROUND_OTHER Any ground unit that does not fall into any other ground category. @@ -929,6 +930,7 @@ WAREHOUSE.Attribute = { GROUND_ARTILLERY="Ground_Artillery", GROUND_TANK="Ground_Tank", GROUND_TRAIN="Ground_Train", + GROUND_EWR="Ground_EWR", GROUND_AAA="Ground_AAA", GROUND_SAM="Ground_SAM", GROUND_OTHER="Ground_OtherGround", @@ -985,7 +987,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.6" +WAREHOUSE.version="0.4.7" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1710,7 +1712,7 @@ function WAREHOUSE:_NewLane(group, startcoord, finalcoord) end -- Add beginning and end. - table.insert(lane, 1, startcoord) + --table.insert(lane, 1, startcoord) table.insert(lane, #lane, finalcoord) return lane @@ -4000,12 +4002,9 @@ function WAREHOUSE:_RouteNaval(group, request) -- Set speed to 80% of max possible. local _speed=group:GetSpeedMax()*0.8 - -- Get shipping lane to remote warehouse. - --local lane=self.shippinglanes[request.warehouse.warehouse:GetName()] - - -- Get off road path to remote warehouse. If more have been defined, pick one randomly. + -- Get shipping lane to remote warehouse. If more have been defined, pick one randomly. local remotename=request.warehouse.warehouse:GetName() - local lane=self.shippinglanes[remotename][math.random(#self.ship[remotename])] + local lane=self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] if lane then @@ -4189,29 +4188,6 @@ function WAREHOUSE:_OnEventLanding(EventData) -- Debug info. self:T(self.wid..string.format("Warehouse %s captured event landing of its asset unit %s.", self.alias, EventData.IniUnitName)) - --[[ - -- Check if all cargo was delivered. - if self.delivered[rid]==true then - - -- Check if helicopter landed in spawn zone. If so, we call it a day and add it back to stock. - if group:GetCategory()==Group.Category.HELICOPTER then - if self.spawnzone:IsCoordinateInZone(EventData.IniUnit:GetCoordinate()) then - - -- Debug message. - self:_DebugMessage("Helicopter landed in spawn zone. No pending request. Putting back into stock.") - if self.Debug then - group:SmokeWhite() - end - - -- Group arrived. - self:Arrived(group) - - end - end - - end - ]] - end end end @@ -4262,9 +4238,12 @@ function WAREHOUSE:_OnEventArrived(EventData) local request=self:_GetRequestOfGroup(group, self.pending) local istransport=self:_GroupIsTransport(group,request) + + -- Check if engine shutdown happend at right airbase because the event is also triggered in other situations. + local rightairbase=group:GetCoordinate():GetClosestAirbase():GetName()==request.warehouse:GetAirbase():GetName() -- Check that group is cargo and not transport. - if istransport==false then + if istransport==false and rightairbase then -- Debug info. local text=string.format("Air asset group %s from warehouse %s arrived at its destination.", group:GetName(), self.alias) @@ -4456,14 +4435,6 @@ function WAREHOUSE:_UnitDead(deadunit, request) end end -end - ---- Remove group. --- @param #WAREHOUSE self --- @param Wrapper.Group#GROUP group The group to be removed. -function WAREHOUSE:_RemoveGroup(group) - - end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -5828,10 +5799,10 @@ function WAREHOUSE:_GetAttribute(group) -- Planes local transportplane=group:HasAttribute("Transports") and group:HasAttribute("Planes") local awacs=group:HasAttribute("AWACS") - local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") - local bomber=group:HasAttribute("Bombers") + local fighter=group:HasAttribute("Fighters") or group:HasAttribute("Interceptors") or group:HasAttribute("Multirole fighters") or (group:HasAttribute("Bombers") and not group:HasAttribute("Strategic bombers")) + local bomber=group:HasAttribute("Strategic bombers") local tanker=group:HasAttribute("Tankers") - local uav=group:HasAttribute("UAV") + local uav=group:HasAttribute("UAVs") -- Helicopters local transporthelo=group:HasAttribute("Transport helicopters") local attackhelicopter=group:HasAttribute("Attack helicopters") @@ -5841,12 +5812,13 @@ function WAREHOUSE:_GetAttribute(group) -------------- -- Ground local apc=group:HasAttribute("Infantry carriers") - local truck=group:HasAttribute("Trucks") and not group:GetCategory()==Group.Category.TRAIN + local truck=group:HasAttribute("Trucks") and group:GetCategory()==Group.Category.GROUND local infantry=group:HasAttribute("Infantry") local artillery=group:HasAttribute("Artillery") local tank=group:HasAttribute("Old Tanks") or group:HasAttribute("Modern Tanks") local aaa=group:HasAttribute("AAA") - local sam=group:HasAttribute("SAM") + local ewr=group:HasAttribute("EWR") + local sam=group:HasAttribute("SAM elements") and (not group:HasAttribute("AAA")) -- Train local train=group:GetCategory()==Group.Category.TRAIN @@ -5879,8 +5851,6 @@ function WAREHOUSE:_GetAttribute(group) attribute=WAREHOUSE.Attribute.AIR_UAV elseif apc then attribute=WAREHOUSE.Attribute.GROUND_APC - elseif truck then - attribute=WAREHOUSE.Attribute.GROUND_TRUCK elseif infantry then attribute=WAREHOUSE.Attribute.GROUND_INFANTRY elseif artillery then @@ -5889,8 +5859,12 @@ function WAREHOUSE:_GetAttribute(group) attribute=WAREHOUSE.Attribute.GROUND_TANK elseif aaa then attribute=WAREHOUSE.Attribute.GROUND_AAA + elseif ewr then + attribute=WAREHOUSE.Attribute.GROUND_EWR elseif sam then - attribute=WAREHOUSE.Attribute.GROUND_SAM + attribute=WAREHOUSE.Attribute.GROUND_SAM + elseif truck then + attribute=WAREHOUSE.Attribute.GROUND_TRUCK elseif train then attribute=WAREHOUSE.Attribute.GROUND_TRAIN elseif aircraftcarrier then From 2d029c640508230faaa62de1378ae53c58e6cad3 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Sun, 16 Sep 2018 12:52:13 +0200 Subject: [PATCH 66/73] Warehouse v0.4.8 Set loadradius no nil for APC dispatcher. Adjustet general image. Use SetAirbase() function in New() Remove Stop() from Destroyed. --- .../Moose/Functional/Warehouse.lua | 83 ++++++++++--------- 1 file changed, 44 insertions(+), 39 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 39abc47c4..c58bde6fe 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -18,7 +18,7 @@ -- ### Authors: **funkyfranky**, FlightControl (cargo dispatcher classes) -- -- @module Functional.Warehouse --- @image Warehouse.JPG +-- @image MOOSE.JPG --- WAREHOUSE class. -- @type WAREHOUSE @@ -281,7 +281,6 @@ -- added two times while Path_2(A->B) was added only once. Hence, the group will choose Path_1 with a probability of 66.6 % while Path_2 is only chosen with -- a probability of 33.3 %. -- --- -- ## Rail Connections -- -- A rail connection is automatically defined as the closest point on a railway measured from the center of the spawn zone. But only, if the distance is less than 3 km. @@ -384,15 +383,15 @@ -- Technically, the capturing of the airbase is triggered by the DCS [S\_EVENT\_BASE\_CAPTURED](https://wiki.hoggitworld.com/view/DCS_event_base_captured) event. -- So the capturing takes place when only enemy ground units are in the airbase zone whilst no ground units of the present airbase owner are in that zone. -- --- The warehouse will also create an event named "AirbaseCaptured", which can be captured by the @{#WAREHOUSE.OnAfterAirbaseCaptured} function. So the warehouse can react on --- this attack and for example spawn ground groups to re-capture its airbase. +-- The warehouse will also create an event **AirbaseCaptured**, which can be captured by the @{#WAREHOUSE.OnAfterAirbaseCaptured} function. So the warehouse chief can react on +-- this attack and for example deploy ground groups to re-capture its airbase. -- --- When an airbase is re-captured the event "AirbaseRecaptured" is triggered and can be captured by the @{#WAREHOUSE.OnAfterAirbaseRecaptured} function. +-- When an airbase is re-captured the event **AirbaseRecaptured** is triggered and can be captured by the @{#WAREHOUSE.OnAfterAirbaseRecaptured} function. -- This can be used to put the defending assets back into the warehouse stock. -- -- ## Capturing the Warehouse -- --- A warehouse can also be captured by the enemy coalition. If enemy ground troops enter the warehouse zone the event **Attacked** is triggered which can be captured by the +-- A warehouse can be captured by the enemy coalition. If enemy ground troops enter the warehouse zone the event **Attacked** is triggered which can be captured by the -- @{#WAREHOUSE.OnAfterAttacked} event. By default the warehouse zone circular zone with a radius of 500 meters located at the center of the physical warehouse. -- The warehouse zone can be set via the @{#WAREHOUSE.SetWarehouseZone}(*zone*) function. The parameter *zone* must also be a cirular zone. -- @@ -723,7 +722,7 @@ -- warehouse.Stennis:AddAsset("CH-53E", 3) -- -- -- Define a "port" at the Stennis to be able to spawn Naval assets. This zone will move behind the Stennis. --- local stenniszone=ZONE_UNIT:New("Spawnzone Stennis", UNIT:FindByName("Stennis"), 100, {rho=250, theta=180, relative_to_unit=true}) +-- local stenniszone=ZONE_UNIT:New("Spawnzone Stennis", UNIT:FindByName("USS Stennis"), 100, {rho=250, theta=180, relative_to_unit=true}) -- warehouse.Stennis:SetPortZone(stenniszone) -- -- -- Self request of rescue helo and speed boats. @@ -738,7 +737,7 @@ -- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem -- -- -- USS Stennis is the mother ship. --- local Mother=UNIT:FindByName("Stennis") +-- local Mother=UNIT:FindByName("USS Stennis") -- -- -- Get assignment for this request. -- local assignment=warehouse.Stennis:GetAssignment(request) @@ -987,12 +986,13 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.7" +WAREHOUSE.version="0.4.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - +-- TODO: Get cargo bay and weight from CARGO_GROUP and GROUP. +-- TODO: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters. -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Test capturing a neutral warehouse. -- TODO: Make more examples: ARTY, CAP, ... @@ -1080,13 +1080,12 @@ function WAREHOUSE:New(warehouse, alias) -- Closest of the same coalition but within a certain range. local _airbase=self:GetCoordinate():GetClosestAirbase(nil, self:GetCoalition()) if _airbase and _airbase:GetCoordinate():Get2DDistance(self:GetCoordinate()) < 3000 then - self.airbase=_airbase - self.airbasename=self.airbase:GetName() + self:SetAirbase(_airbase) end -- Define warehouse and default spawn zone. self.zone=ZONE_RADIUS:New(string.format("Warehouse zone %s", self.warehouse:GetName()), warehouse:GetVec2(), 500) - self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 200) + self.spawnzone=ZONE_RADIUS:New(string.format("Warehouse %s spawn zone", self.warehouse:GetName()), warehouse:GetVec2(), 250) -- Add warehouse to database. WAREHOUSE.db.Warehouses[self.uid]=self @@ -1422,16 +1421,16 @@ function WAREHOUSE:New(warehouse, alias) -- @param DCS#coalition.side Coalition Coalition which re-captured the airbase, i.e. the same as the current warehouse owner coalition. - --- Triggers the FSM event "Destroyed" when the warehouse was destroyed. All services are stopped. + --- Triggers the FSM event "Destroyed" when the warehouse was destroyed. Services are stopped. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] Destroyed - --- Triggers the FSM event "Destroyed" with a delay when the warehouse was destroyed. All services are stopped. + --- Triggers the FSM event "Destroyed" with a delay when the warehouse was destroyed. Services are stopped. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] Destroyed -- @param #number delay Delay in seconds. - --- On after "Destroyed" event user function. Called when the warehouse was destroyed. All services are stopped. + --- On after "Destroyed" event user function. Called when the warehouse was destroyed. Services are stopped. -- @param #WAREHOUSE self -- @function [parent=#WAREHOUSE] OnAfterDestroyed -- @param #string From From state. @@ -1529,7 +1528,11 @@ end -- @return #WAREHOUSE self function WAREHOUSE:SetAirbase(airbase) self.airbase=airbase - self.airbasename=airbase:GetName() + if airbase~=nil then + self.airbasename=airbase:GetName() + else + self.airbasename=nil + end return self end @@ -1695,7 +1698,7 @@ function WAREHOUSE:_NewLane(group, startcoord, finalcoord) local distF=startcoord:Get2DDistance(coordF) local distL=startcoord:Get2DDistance(coordL) - -- Add the shipping lane. Need to take care of the wrong "direction". + -- Add the lane. Need to take care of the wrong "direction". local lane={} if distF ishome=%s", group:GetName(), speed, tostring(onground), airbase, tostring(inspawnzone), tostring(ishome)) - self:E(self.wid..text) + self:T(self.wid..text) if ishome then -- Info message. - self:_InfoMessage(string.format("Warehouse %s: No cargo assets left for request id=%s. Remaining %s transport assets go back into stock!", self.alias, request.uid, ntransport)) + local text=string.format("Warehouse %s: Transport group arrived back home and no cargo left for request id=%d.\nSending transport group %s back to stock.", self.alias, request.uid, group:GetName()) + self:_InfoMessage(text) + -- Debug smoke. if self.Debug then group:SmokeRed() end @@ -2453,7 +2451,8 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu -- Destroy group if it is alive. if group:IsAlive()==true then self:_DebugMessage(string.format("Destroying group %s.", group:GetName()), 5) - group:Destroy() + -- Setting parameter to false, i.e. creating NO dead or remove unit event, seems to not confuse the dispatcher logic. + group:Destroy(false) end else @@ -2894,7 +2893,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Add groups to cargo if they don't go by themselfs. local CargoGroups --Core.Set#SET_CARGO - --TODO: make nearradius depended on transport type and asset type. + -- Load radius and near radius. local _loadradius=5000 local _nearradius=nil @@ -2904,7 +2903,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) --_loadradius=1000 _loadradius=nil elseif Request.transporttype==WAREHOUSE.TransportType.APC then - _loadradius=1000 + _loadradius=nil end --_loadradius=nil @@ -3568,7 +3567,8 @@ function WAREHOUSE:onafterDestroyed(From, Event, To) self:_InfoMessage(text) -- Stop warehouse FSM in one minute. - self:__Stop(60) + -- Maybe dont stop it or pending requests are not updated any more. + --self:__Stop(60) end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3580,7 +3580,7 @@ end -- @param #WAREHOUSE.Queueitem Request Information table of the request. -- @return Core.Set#SET_GROUP Set of groups that were spawned. function WAREHOUSE:_SpawnAssetRequest(Request) - self:E({requestUID=Request.uid}) + self:F2({requestUID=Request.uid}) -- Shortcut to cargo assets. local _assetstock=Request.cargoassets @@ -5103,8 +5103,13 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- Get all transports of the requested type in stock. local transports=self:_FilterStock(self.stock, WAREHOUSE.Descriptor.ATTRIBUTE, request.transporttype) - -- Copy asset. - local cargoassets=UTILS.DeepCopy(request.cargoassets) + -- Copy asset. + local cargoassets=UTILS.DeepCopy(request.cargoassets) + local cargoset=request.transportcargoset + + -- TODO: Get weight and cargo bay from CARGO_GROUP + --local cargogroup=CARGO_GROUP:New(CargoGroup,Type,Name,LoadRadius,NearRadius) + --cargogroup:GetWeight() -- Sort transport carriers w.r.t. cargo bay size. local function sort_transports(a,b) @@ -5955,7 +5960,7 @@ function WAREHOUSE:_DeleteQueueItem(qitem, queue) for i=1,#queue do local _item=queue[i] --#WAREHOUSE.Queueitem if _item.uid==qitem.uid then - self:I(self.wid..string.format("Deleting queue item %d.", qitem.uid)) + self:T(self.wid..string.format("Deleting queue item id=%d.", qitem.uid)) table.remove(queue,i) break end From 98b427a26fb1028dcf18852f0def9b30a44e1394 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 17 Sep 2018 00:23:04 +0200 Subject: [PATCH 67/73] Warehouse v0.4.9 Added option to force cargo bay limit and cargo asset weight. --- Moose Development/Moose/AI/AI_Cargo.lua | 8 +- .../Moose/Functional/Warehouse.lua | 120 ++++++++++++++---- .../Moose/Wrapper/Positionable.lua | 2 +- 3 files changed, 100 insertions(+), 30 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Cargo.lua b/Moose Development/Moose/AI/AI_Cargo.lua index 1d358b735..18155df55 100644 --- a/Moose Development/Moose/AI/AI_Cargo.lua +++ b/Moose Development/Moose/AI/AI_Cargo.lua @@ -142,9 +142,11 @@ function AI_CARGO:New( Carrier, CargoSet ) -- @param #string Event -- @param #string To - for _, CarrierUnit in pairs( Carrier:GetUnits() ) do - CarrierUnit:SetCargoBayWeightLimit() - end + -- FF "Workaround" for not being able to set the cargo bay limit manually for the carrier group. + -- FF Moreover, the carrier group is an input parameter and should not be overwritten here. + --for _, CarrierUnit in pairs( Carrier:GetUnits() ) do + --CarrierUnit:SetCargoBayWeightLimit() + --end self.Transporting = false self.Relocating = false diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index c58bde6fe..07c20d394 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -986,7 +986,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.8" +WAREHOUSE.version="0.4.9" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1090,6 +1090,10 @@ function WAREHOUSE:New(warehouse, alias) -- Add warehouse to database. WAREHOUSE.db.Warehouses[self.uid]=self + ----------------------- + --- FSM Transitions --- + ----------------------- + -- Start State. self:SetStartState("NotReadyYet") @@ -1176,6 +1180,8 @@ function WAREHOUSE:New(warehouse, alias) -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. -- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. + -- @param #number forcecargobay (Optional) Explicitly force cargobay weight limit in kg for cargo carriers. This is for each *unit* of the group. + -- @param #number forceweight (Optional) Explicitly force weight in kg of each unit in the group. --- Trigger the FSM event "AddAsset" with a delay. Add a group to the warehouse stock. -- @function [parent=#WAREHOUSE] __AddAsset @@ -1184,6 +1190,8 @@ function WAREHOUSE:New(warehouse, alias) -- @param Wrapper.Group#GROUP group Group to be added as new asset. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. -- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. + -- @param #number forcecargobay (Optional) Explicitly force cargobay weight limit in kg for cargo carriers. This is for each *unit* of the group. + -- @param #number forceweight (Optional) Explicitly force weight in kg of each unit in the group. --- Triggers the FSM event "AddRequest". Add a request to the warehouse queue, which is processed when possible. @@ -2178,7 +2186,7 @@ function WAREHOUSE:onafterStatus(From, Event, To) -- Display complete list of stock itmes. if self.Debug then - --self:_DisplayStockItems(self.stock) + self:_DisplayStockItems(self.stock) end -- Call status again in ~30 sec (user choice). @@ -2374,7 +2382,9 @@ end -- @param Wrapper.Group#GROUP group Group or template group to be added to the warehouse stock. -- @param #number ngroups Number of groups to add to the warehouse stock. Default is 1. -- @param #WAREHOUSE.Attribute forceattribute (Optional) Explicitly force a generalized attribute for the asset. This has to be an @{#WAREHOUSE.Attribute}. -function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribute) +-- @param #number forcecargobay (Optional) Explicitly force cargobay weight limit in kg for cargo carriers. This is for each *unit* of the group. +-- @param #number forceweight (Optional) Explicitly force weight in kg of each unit in the group. +function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribute, forcecargobay, forceweight) -- Set default. local n=ngroups or 1 @@ -2439,7 +2449,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu self:_DebugMessage(self.wid..string.format("Warehouse %s: Adding %d NEW assets of group %s to stock.", self.alias, n, tostring(group:GetName())), 5) -- This is a group that is not in the db yet. Add it n times. - local assets=self:_RegisterAsset(group, n, forceattribute) + local assets=self:_RegisterAsset(group, n, forceattribute, forcecargobay, forceweight) -- Add created assets to stock of this warehouse. for _,asset in pairs(assets) do @@ -2468,8 +2478,10 @@ end -- @param Wrapper.Group#GROUP group The group that will be added to the warehouse stock. -- @param #number ngroups Number of groups to be added. -- @param #string forceattribute Forced generalized attribute. +-- @param #number forcecargobay Cargo bay weight limit in kg. +-- @param #number forceweight Weight of units in kg. -- @return #table A table containing all registered assets. -function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) +function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, forceweight) self:F({groupname=group:GetName(), ngroups=ngroups, forceattribute=forceattribute}) -- Set default. @@ -2506,15 +2518,30 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute) local Desc=unit:GetDesc() -- Weight. We sum up all units in the group. - local unitweight=Desc.massEmpty + local unitweight=forceweight or Desc.massEmpty if unitweight then weight=weight+unitweight end + local cargomax=0 + local massfuel=Desc.fuelMassMax or 0 + local massempty=Desc.massEmpty or 0 + local massmax=Desc.massMax or 0 + + -- Calcuate cargo bay limit value. + cargomax=massmax-massfuel-massempty + self:T3(self.wid..string.format("Unit name=%s: mass empty=%.1f kg, fuel=%.1f kg, max=%.1f kg ==> cargo=%.1f kg", unit:GetName(), unitweight, massfuel, massmax, cargomax)) + -- Cargo bay size. - local bay=unit:GetCargoBayFreeWeight() + local bay=forcecargobay or unit:GetCargoBayFreeWeight() + + -- Add bay size to table. table.insert(cargobay, bay) + + -- Sum up total bay size. cargobaytot=cargobaytot+bay + + -- Get max bay size. if bay>cargobaymax then cargobaymax=bay end @@ -2912,9 +2939,26 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Empty cargo group set. CargoGroups = SET_CARGO:New() + local function getasset(group) + local _,aid,_=self:_GetIDsFromGroup(group) + self:FindAssetInDB(group) + end + -- Add cargo groups to set. for _i,_group in pairs(_spawngroups:GetSetObjects()) do - CargoGroups:AddCargo(CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_loadradius,_nearradius)) + + -- New cargo group object. + local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_loadradius,_nearradius) + + -- Find asset belonging to this group. + local asset=self:FindAssetInDB(_group) + if asset then + -- Set weight for this group. + cargogroup:SetWeight(asset.weight) + end + + -- Add group to group set. + CargoGroups:AddCargo(cargogroup) end ------------------------------------------------------------------------------------------------------------------------------------ @@ -3017,20 +3061,31 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- 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:GetSetObjects()) do - for _,carrierunit in pairs(carriergroup:GetUnits()) do - Pending.carriercargo[carrierunit:GetName()]={} - end - end -- Add request to pending queue. table.insert(self.pending, Pending) -- Delete request from queue. - self:_DeleteQueueItem(Request, self.queue) + self:_DeleteQueueItem(Request, self.queue) + + -- Adjust carrier units. This has to come AFTER the dispatchers have been defined because they set the cargobay free weight! + Pending.carriercargo={} + for _,carriergroup in pairs(TransportSet:GetSetObjects()) do + local asset=self:FindAssetInDB(carriergroup) + for _i,_carrierunit in pairs(carriergroup:GetUnits()) do + local carrierunit=_carrierunit --Wrapper.Unit#UNIT + + -- Create empty tables which will be filled with the cargo groups of each carrier unit. Needed in case a carrier unit dies. + Pending.carriercargo[carrierunit:GetName()]={} + + -- Adjust cargo bay of carrier unit. + local cargobay=asset.cargobay[_i] + carrierunit:SetCargoBayWeightLimit(cargobay) + + -- Debug info. + self:T2(self.wid..string.format("Cargo bay weight limit ofcarrier unit %s: %.1f kg.", carrierunit:GetName(), carrierunit:GetCargoBayFreeWeight())) + end + end ------------------------ -- Create Dispatchers -- @@ -3656,7 +3711,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Add group to group set and asset list. if _group then _groupset:AddGroup(_group) - table.insert(_assets, _assetitem) + table.insert(_assets, _assetitem) else self:E(self.wid.."ERROR: Cargo asset could not be spawned!") end @@ -4989,7 +5044,8 @@ function WAREHOUSE:_CheckRequestNow(request) if not _enough then local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) self:_InfoMessage(text, 5) - + text=string.format("Enough=%s, #_assets=%d, _nassets=%d, request.nasset=%s", tostring(_enough), #_assets,_nassets, tostring(request.nasset)) + env.info(text) return false end @@ -5178,7 +5234,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) --self:E(self.wid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) -- Cargo fits into carrier - if delta>0 then + if delta>=0 then -- Reduce remaining cargobay. cargobay=cargobay-asset.weight self:T3(self.wid..string.format("%s unit %d loads cargo uid=%d: bayempty=%02d, bayloaded = %02d - weight=%02d", transport.templatename, k, asset.uid, transport.cargobay[k], cargobay, asset.weight)) @@ -5189,7 +5245,7 @@ function WAREHOUSE:_GetTransportsForAssets(request) -- This transport group is used. used=true else - self:T2(self.wid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be not used! Cargo bay - asset weight = %d kg", transport.templatename, asset.templatename, delta)) + self:T2(self.wid..string.format("Carrier unit %s too small for cargo asset %s ==> cannot be used! Cargo bay - asset weight = %d kg", transport.templatename, asset.templatename, delta)) end end -- loop over assets @@ -6118,14 +6174,26 @@ end -- @param #table stock Table holding all assets in stock of the warehouse. Each entry is of type @{#WAREHOUSE.Assetitem}. function WAREHOUSE:_DisplayStockItems(stock) - local text=self.wid..string.format("Warehouse %s stock assets:\n", self.airbase:GetName()) - for _,_stock in pairs(stock) do + local text=self.wid..string.format("Warehouse %s stock assets:", self.airbase:GetName()) + for _i,_stock in pairs(stock) do local mystock=_stock --#WAREHOUSE.Assetitem - text=text..string.format("template = %s, category = %d, unittype = %s, attribute = %s\n", mystock.templatename, mystock.category, mystock.unittype, mystock.attribute) + local name=mystock.templatename + local category=mystock.category + local cargobaymax=mystock.cargobaymax + local cargobaytot=mystock.cargobaytot + local nunits=mystock.nunits + local range=mystock.range + local size=mystock.size + local speed=mystock.speedmax + local uid=mystock.uid + local unittype=mystock.unittype + local weight=mystock.weight + local attribute=mystock.attribute + text=text..string.format("\n%02d) uid=%d, name=%s, unittype=%s, category=%d, attribute=%s, nunits=%d, speed=%.1f km/h, range=%.1f km, size=%.1f m, weight=%.1f kg, cargobax max=%.1f kg tot=%.1f kg", + _i, uid, name, unittype, category, attribute, nunits, speed, range/1000, size, weight, cargobaymax, cargobaytot) end - env.info(text) - MESSAGE:New(text, 10):ToAll() + self:T3(text) end --- Fireworks! diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 86c3c4240..7a37c7426 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1019,7 +1019,7 @@ do -- Cargo -- self.__.CargoBayVolumeLimit = VolumeLimit -- end - --- Get Cargo Bay Weight Limit in kg. + --- Set Cargo Bay Weight Limit in kg. -- @param #POSITIONABLE self -- @param #number WeightLimit function POSITIONABLE:SetCargoBayWeightLimit( WeightLimit ) From f4f942d8d4c00cb3977ea30aa314f8d02d6e34c2 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 17 Sep 2018 14:04:30 +0200 Subject: [PATCH 68/73] Warehouse little updates static countryid --- Moose Development/Moose/Core/SpawnStatic.lua | 4 +--- Moose Development/Moose/Wrapper/Static.lua | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 5fa87ddb6..d1b9cf371 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -200,10 +200,8 @@ function SPAWNSTATIC:ReSpawn(countryid) if StaticTemplate then - local CountryID = countryid or self.CountryID - local CountryName = _DATABASE.COUNTRY_NAME[CountryID] + --local CountryID = countryid or (self.CountryID or CountryID) - StaticTemplate.units = nil StaticTemplate.route = nil StaticTemplate.groupId = nil diff --git a/Moose Development/Moose/Wrapper/Static.lua b/Moose Development/Moose/Wrapper/Static.lua index fd20f3328..e624dc021 100644 --- a/Moose Development/Moose/Wrapper/Static.lua +++ b/Moose Development/Moose/Wrapper/Static.lua @@ -197,9 +197,9 @@ end -- @param DCS#country.id countryid The country ID used for spawning the new static. function STATIC:ReSpawn(countryid) - local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName ) + local SpawnStatic = SPAWNSTATIC:NewFromStatic( self.StaticName, countryid ) - SpawnStatic:ReSpawn(countryid) + SpawnStatic:ReSpawn() end From 0275ba8b6b62179159928874dce26e63efa74f98 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 17 Sep 2018 14:04:55 +0200 Subject: [PATCH 69/73] warehouse little changes --- .../Moose/Functional/Warehouse.lua | 87 +++++++++++-------- 1 file changed, 51 insertions(+), 36 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 07c20d394..000347120 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -164,9 +164,11 @@ -- * *Prio*: (Optional) A number between 1 (high) and 100 (low) describing the priority of the request. Request with high priority are processed first. Default is 50, i.e. medium priority. -- * *Assignment*: (Optional) A free to choose string describing the assignment. For self requests, this can be used to assign the spawned groups to specific tasks. -- +-- ## Requesting by Generalized Attribute +-- -- For example: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2, 20) +-- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2) -- -- Here, warehouse Kobuleti requests 5 infantry groups from warehouse Batumi. These "cargo" assets should be transported from Batumi to Kobuleti by 2 APCS. -- Note that the warehouse at Batumi needs to have at least five infantry groups and two APC groups in their stock if the request can be processed. @@ -184,7 +186,7 @@ -- Here, Kobuleti requests a specific unit type, in particular two groups of A-10Cs. Note that the spelling is important as it must exacly be the same as -- what one get's when using the DCS unit type. -- --- ## Requesting a Specifc Group +-- ## Requesting a Specific Group -- -- An even more specific request would be: -- @@ -192,7 +194,7 @@ -- -- In this case three groups named "Group Name as in ME" are requested. So this explicitly request the groups named like that in the Mission Editor. -- --- ## Requesting a general category +-- ## Requesting a General Category -- -- On the other hand, very general unspecifc requests can be made as -- @@ -200,6 +202,8 @@ -- -- Here, Kubuleti requests 10 ground groups and does not care which ones. This could be a mix of infantry, APCs, trucks etc. -- +-- **Note** that these general requests should be made with *great care* due to the fact, that depending on what a warehouse has in stock a lot of different unit types can be spawned. +-- -- # Employing Assets -- -- Assets in the warehouse' stock can used for user defined tasks realtively easily. They can be spawned into the game by a "self request", i.e. the warehouse @@ -1655,8 +1659,13 @@ function WAREHOUSE:AddOffRoadPath(remotewarehouse, group, oneway) -- Create new path from template group waypoints. local path=self:_NewLane(group, startcoord, finalcoord) + if path==nil then + self:E(self.wid.."ERROR: Offroad path could not be added. Group present in ME?") + return + end + -- Debug info. Marks along path. - if self.Debug then + if path and self.Debug then for i=1,#path do local coord=path[i] --Core.Point#COORDINATE local text=string.format("Off road path from %s to %s. Point %d.", self.alias, remotewarehouse.alias, i) @@ -1691,40 +1700,46 @@ end -- @return #table Table with route points. function WAREHOUSE:_NewLane(group, startcoord, finalcoord) - -- Get route from template. - local lanepoints=group:GetTemplateRoutePoints() - - -- First and last waypoints - local laneF=lanepoints[1] - local laneL=lanepoints[#lanepoints] - - -- Get corresponding coordinates. - local coordF=COORDINATE:New(laneF.x, 0, laneF.y) - local coordL=COORDINATE:New(laneL.x, 0, laneL.y) - - -- Figure out which point is closer to the port of this warehouse. - local distF=startcoord:Get2DDistance(coordF) - local distL=startcoord:Get2DDistance(coordL) - - -- Add the lane. Need to take care of the wrong "direction". - local lane={} - if distF Date: Tue, 18 Sep 2018 00:00:19 +0200 Subject: [PATCH 70/73] Warehouse v0.5.0 Improved documentation including examples. Fixed destroy state. Added pictures. --- .../Moose/Functional/Warehouse.lua | 636 ++++++++++++++---- 1 file changed, 488 insertions(+), 148 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 000347120..2cd00d6a4 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -9,9 +9,11 @@ -- * Holds (virtual) assests in stock. -- * Manages requests of assets from other warehouses. -- * Realistic transportation of assets between warehouses. --- * Different means of automatic transportation (planes, helicopters, APCs, selfpropelled). +-- * Different means of automatic transportation (planes, helicopters, APCs, self propelled). -- * Strategic components such as capturing, defending and destroying warehouses and their associated infrastructure. --- * Can be coupled to other MOOSE classes. +-- * Can be easily interfaced to other MOOSE classes. +-- +-- Please not that his class is work in progress and in an **alpha** stage. -- -- === -- @@ -54,7 +56,7 @@ -- -- === -- --- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.jpg) +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Main.png) -- -- # The Warehouse Concept -- @@ -95,9 +97,9 @@ -- Furthermore, ground assets can be transferred between warehouses by transport units. These are APCs, helicopters and airplanes. The transportation process is modelled -- in a realistic way by using the corresponding cargo dispatcher classes, i.e. -- --- * @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC}, --- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} and --- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE}. +-- * @{AI.AI_Cargo_Dispatcher_APC#AI_DISPATCHER_APC} +-- * @{AI.AI_Cargo_Dispatcher_Helicopter#AI_DISPATCHER_HELICOPTER} +-- * @{AI.AI_Cargo_Dispatcher_Airplane#AI_DISPATCHER_AIRPLANE} -- -- Depending on which cargo dispatcher is used (ground or airbore), similar considerations like in the self propelled case are necessary. Howver, note that -- the dispatchers as of yet cannot use user defined off road paths for example since they are classes of their own and use a different routing logic. @@ -130,7 +132,7 @@ -- -- # Adding Assets -- --- 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}. +-- Assets can be added to the warehouse stock by using the @{#WAREHOUSE.AddAsset}(*group*, *ngroups*, *forceattribute*, *forcecargobay*, *forceweight*) 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. -- -- @@ -140,11 +142,36 @@ -- 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. -- --- 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. +-- You can add assets with a delay by using the @{#WAREHOUSE.__AddAsset}(*delay*, *group*, *ngroups*, *foceattribute*, *forcecargobay*, *forceweight*), where *delay* +-- is the delay in seconds before the asset is added. +-- +-- In game, the warehouse will get a mark which is regularly updated and showing the currently available assets in stock. +-- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Stock-Marker.png) +-- +-- ## Options for Fine Tuning -- -- 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}. -- +-- ### Setting the Generalized Attibute +-- For example, a UH-1H Huey has in DCS the attibute of an attack helicopter. But of course, it can also transport cargo. If you want to use it for transportation, you can specify this +-- manually when the asset is added +-- +-- warehouse.Batumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) +-- +-- ### Setting the Cargo Bay Weight Limit +-- You can also ajust the cargo bay weight limit, in case it is not calculated correctly automatically. For example, the cargo bay of a C-17A is much smaller in DCS than that of a C-130, which is +-- unrealistic. This can be corrected by the *forcecargobay* parmeter which is here set to 77,000 kg +-- +-- warehouse.Batumi:AddAsset("C-17A", nil, 77000) +-- +-- ### Setting the Weight +-- In the current version of DCS a mortar unit has a weight of 5 tons. This confuses the transporter logic, because it appears to be too have for, e.g. all APCs. You can manually adjust the weight +-- by the *forceweight* parameter and set it to 210 kg for each unit in the group +-- +-- warehouse.Batumi:AddAsset("Mortar Alpha", nil, nil, nil, 210) +-- -- === -- -- # Requesting Assets @@ -168,7 +195,7 @@ -- -- For example: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2) +-- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2) -- -- Here, warehouse Kobuleti requests 5 infantry groups from warehouse Batumi. These "cargo" assets should be transported from Batumi to Kobuleti by 2 APCS. -- Note that the warehouse at Batumi needs to have at least five infantry groups and two APC groups in their stock if the request can be processed. @@ -176,12 +203,13 @@ -- transport assets are available. -- -- Also note that the above request is for five infantry groups. So any group in stock that has the generalized attribute "INFANTRY" can be selected. +-- -- -- ## Requesting a Specific Unit Type -- -- A more specific request could look like: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.UNITTYPE, "A-10C", 2) +-- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.UNITTYPE, "A-10C", 2) -- -- Here, Kobuleti requests a specific unit type, in particular two groups of A-10Cs. Note that the spelling is important as it must exacly be the same as -- what one get's when using the DCS unit type. @@ -190,15 +218,15 @@ -- -- An even more specific request would be: -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Group Name as in ME", 3) +-- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Group Name as in ME", 3) -- --- In this case three groups named "Group Name as in ME" are requested. So this explicitly request the groups named like that in the Mission Editor. +-- In this case three groups named "Group Name as in ME" are requested. This explicitly request the groups named like that in the Mission Editor. -- -- ## Requesting a General Category -- -- On the other hand, very general unspecifc requests can be made as -- --- warehouseBatumi:AddRequest(warehouseKobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.Ground, 10) +-- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.Ground, 10) -- -- Here, Kubuleti requests 10 ground groups and does not care which ones. This could be a mix of infantry, APCs, trucks etc. -- @@ -206,10 +234,10 @@ -- -- # Employing Assets -- --- Assets in the warehouse' stock can used for user defined tasks realtively easily. They can be spawned into the game by a "self request", i.e. the warehouse +-- Assets in the warehouse' stock can used for user defined tasks realtively easily. They can be spawned into the game by a "*self request*", i.e. the warehouse -- requests the assets from itself: -- --- warehouseBatumi:AddRequest(warehouseBatumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5) +-- warehouse.Batumi:AddRequest(warehouse.Batumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5) -- -- This would simply spawn five infantry groups in the spawn zone of the Batumi warehouse if/when they are available. -- @@ -266,7 +294,8 @@ -- By default, the closest point on road to the center of the spawn zone is choses as road connection automatically. But only, if distance between the spawn zone -- and the road connection is less than 3 km. -- --- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. +-- The user can set the road connection manually with the @{#WAREHOUSE.SetRoadConnection} function. This is only functional for self propelled assets at the moment +-- and not if using the AI dispatcher classes since these have a different logic to find the route. -- -- ## Off Road Connections -- @@ -277,6 +306,8 @@ -- The parameter *group* is a late activated template group. The waypoints of this group are used to define the path between the two warehouses. -- By default, the reverse paths is automatically added to get *from* the remote warehouse to this warehouse unless the parameter *oneway* is set to *true*. -- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Off-RoadPaths.png) +-- -- **Note** that if an off road connection is defined between two warehouses this becomes the default path, i.e. even if there is a path *on road* possible -- this will not be used. -- @@ -437,6 +468,7 @@ -- -- -- Define Warehouses. -- local warehouse={} +-- -- Blue warehouses -- warehouse.Senaki = WAREHOUSE:New(STATIC:FindByName("Warehouse Senaki"), "Senaki") --Functional.Warehouse#WAREHOUSE -- warehouse.Batumi = WAREHOUSE:New(STATIC:FindByName("Warehouse Batumi"), "Batumi") --Functional.Warehouse#WAREHOUSE -- warehouse.Kobuleti = WAREHOUSE:New(STATIC:FindByName("Warehouse Kobuleti"), "Kobuleti") --Functional.Warehouse#WAREHOUSE @@ -444,6 +476,11 @@ -- warehouse.Berlin = WAREHOUSE:New(STATIC:FindByName("Warehouse Berlin"), "Berlin") --Functional.Warehouse#WAREHOUSE -- warehouse.London = WAREHOUSE:New(STATIC:FindByName("Warehouse London"), "London") --Functional.Warehouse#WAREHOUSE -- warehouse.Stennis = WAREHOUSE:New(STATIC:FindByName("Warehouse Stennis"), "Stennis") --Functional.Warehouse#WAREHOUSE +-- warehouse.Pampa = WAREHOUSE:New(STATIC:FindByName("Warehouse Pampa"), "Pampa") --Functional.Warehouse#WAREHOUSE +-- -- Red warehouses +-- warehouse.Sukhumi = WAREHOUSE:New(STATIC:FindByName("Warehouse Sukhumi"), "Sukhumi") --Functional.Warehouse#WAREHOUSE +-- warehouse.Gudauta = WAREHOUSE:New(STATIC:FindByName("Warehouse Gudauta"), "Gudauta") --Functional.Warehouse#WAREHOUSE +-- warehouse.Sochi = WAREHOUSE:New(STATIC:FindByName("Warehouse Sochi"), "Sochi") --Functional.Warehouse#WAREHOUSE -- -- Remarks: -- @@ -452,6 +489,10 @@ -- -- **NOTE** that all examples below need this bit or code at the beginning - or at least the warehouses which are used. -- +-- The example mission is based on the same template mission, which has defined a lot of airborne, ground and naval assets as templates. Only few of those are used here. +-- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Assets.png) +-- -- ## Example 1: Self Request -- -- Ground troops are taken from the Batumi warehouse stock and spawned in its spawn zone. After a short delay, they are added back to the warehouse stock. @@ -477,7 +518,6 @@ -- -- -- Gree smoke on spawned group. -- group:SmokeGreen() --- group:FlareRed() -- -- -- Put asset back to stock after 10 seconds. -- warehouse.Batumi:__AddAsset(10, group) @@ -499,9 +539,9 @@ -- -- Start Warehouse at Batumi. -- warehouse.Batumi:Start() -- --- -- Add 20 infantry groups as assets at Batumi. +-- -- Add 20 infantry groups and ten APCs as assets at Batumi. -- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) --- warehouse.Batumi:AddAsset("TPz Fuchs", 5) +-- warehouse.Batumi:AddAsset("TPz Fuchs", 10) -- -- -- Start Warehouse Berlin. -- warehouse.Berlin:Start() @@ -515,33 +555,35 @@ -- -- ## Example 3: Self Propelled Airborne Assets -- --- Warehouse Senaki receives requests from Kutaisi for one Yak-52s and from FARP London for three Hueys. --- Assets are spawned in Senaki and make their way to the requesting warehouses. +-- Warehouse Senaki receives a high priority request from Kutaisi for one Yak-52s. At the same time, Kobuleti requests half of +-- all available Yak-52s. Request from Kutaisi is first executed and then Kobuleti gets half of the remaining assets. +-- Additionally, London requests one third of all available UH-1H Hueys from Senaki. -- Once the units have arrived they are added to the stock of the receiving warehouses and can be used for further assignments. -- --- -- Start sending warehouse. +-- -- Start warehouses -- warehouse.Senaki:Start() --- --- -- Add assets. --- warehouse.Senaki:AddAsset("Yak-52", 10) --- warehouse.Senaki:AddAsset("Huey", 10) --- --- -- Start receiving warehouses -- warehouse.Kutaisi:Start() +-- warehouse.Kobuleti:Start() -- warehouse.London:Start() -- --- -- Kusaisi requests one Yak-52 form Senaki. FARP London requests three UH-1H Huys from Senaki. --- warehouse.Senaki:AddRequest(warehouse.Kutaisi, WAREHOUSE.Descriptor.TEMPLATENAME, "Yak-52", 1) --- warehouse.Senaki:AddRequest(warehouse.London, WAREHOUSE.Descriptor.TEMPLATENAME, "Huey", 3) +-- -- Add assets to Senaki warehouse. +-- warehouse.Senaki:AddAsset("Yak-52", 10) +-- warehouse.Senaki:AddAsset("Huey", 6) +-- +-- -- Kusaisi requests 3 Yak-52 form Senaki while Kobuleti wants all the rest. +-- warehouse.Senaki:AddRequest(warehouse.Kutaisi, WAREHOUSE.Descriptor.TEMPLATENAME, "Yak-52", 1, nil, nil, 10) +-- warehouse.Senaki:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Yak-52", WAREHOUSE.Quantity.HALF, nil, nil, 70) +-- +-- -- FARP London wants 1/3 of the six available Hueys. +-- warehouse.Senaki:AddRequest(warehouse.London, WAREHOUSE.Descriptor.TEMPLATENAME, "Huey", WAREHOUSE.Quantity.THIRD) -- -- ## Example 4: Transport of Assets by APCs -- --- Warehouse at FARP Berlin requests three infantry groups from Batumi. These assets shall be transported using one APC. --- Infantry and APC are spawned in the spawn zone at Batumi. The APC picks up two of the three infantry groups and --- drives them to Berlin. There, they unboard and walk to the warehouse where they will be added to the stock. --- Meanwhile the APC drives back and picks up the last infantry group and also brings it to Batumi. --- The APC will then return to Batumi and be added back to the stock of the Batumi warehouse. --- The reason that the APC has to drive twice, it that can only up to ten soldiers. +-- Warehouse at FARP Berlin requests five infantry groups from Batumi. These assets shall be transported using two APC groups. +-- Infantry and APC are spawned in the spawn zone at Batumi. The APCs have a cargo bay large enough to pick up four of the +-- five infantry groups in the first run and will bring them to Berlin. There, they unboard and walk to the warehouse where they will be added to the stock. +-- Meanwhile the APCs go back to Batumi and one will pick up the last remaining soldiers. +-- Once the APCs have completed their mission, they return to Batumi and are added back to stock. -- -- -- Start Warehouse at Batumi. -- warehouse.Batumi:Start() @@ -553,15 +595,17 @@ -- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) -- warehouse.Batumi:AddAsset("TPz Fuchs", 5) -- --- -- Warehouse Berlin requests 3 infantry groups from warehouse Batumi using 1 APC for transport. --- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 3, WAREHOUSE.TransportType.APC, 1) +-- -- Warehouse Berlin requests 5 infantry groups from warehouse Batumi using 2 APCs for transport. +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.APC, 2) -- --## Example 5: Transport of Assets by Helicopters -- --- Warehouse at FARP Berlin requests 10 infantry groups from Batumi. They shall be transported by one helicopter. +-- Warehouse at FARP Berlin requests five infantry groups from Batumi. They shall be transported by all available transport helicopters. -- Note that the UH-1H Huey in DCS is an attack and not a transport helo. So the warehouse logic would be default also -- register it as an @{#WAREHOUSE.Attribute.AIR_ATTACKHELICOPTER}. In order to use it as a transport we need to force -- it to be added as transport helo. +-- Also note that even though all (here five) helos are requested, only two of them are employed because this number is sufficient to +-- transport all requested assets in one go. -- -- -- Start Warehouses. -- warehouse.Batumi:Start() @@ -570,26 +614,27 @@ -- -- Add 20 infantry groups as assets at Batumi. -- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) -- --- -- Add five Hueys for transport. Note that the Huey in DCS is an attack and not a transport helo. So we force the attribute! +-- -- Add five Hueys for transport. Note that a Huey in DCS is an attack and not a transport helo. So we force this attribute! -- warehouse.Batumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) -- --- -- Warehouse Berlin requests 10 infantry groups from warehouse Batumi using one huey for transport. --- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 10, WAREHOUSE.TransportType.HELICOPTER, 1) +-- -- Warehouse Berlin requests 5 infantry groups from warehouse Batumi using all available helos for transport. +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 5, WAREHOUSE.TransportType.HELICOPTER, WAREHOUSE.Quantity.ALL) -- --## Example 6: Transport of Assets by Airplanes -- --- Kutaisi requests 20 infantry groups from Senaki. These assets will be loaded into one C-130 cargo plane. +-- Warehoues Kobuleti requests all (three) APCs from Batumi using one airplane for transport. +-- The available C-130 is able to carry one APC at a time. So it has to commute three times between Batumi and Kobuleti to deliver all requested cargo assets. +-- Once the cargo is delivered, the C-130 transport returns to Batumi and is added back to stock. -- --- -- Start Warehouses. --- warehouse.Senaki:Start() --- warehouse.Kutaisi:Start() +-- -- Start warehouses. +-- warehouse.Batumi:Start() +-- warehouse.Kobuleti:Start() -- --- -- Add 20 infantry groups and 5 C-130 transport planes as assets to Senaki warehouse. --- warehouse.Senaki:AddAsset("Infantry Platoon Alpha", 20) --- warehouse.Senaki:AddAsset("C-130", 5) --- --- -- Warehouse Berlin requests 10 infantry groups from warehouse Batumi using 3 APCs for transport. --- warehouse.Senaki:AddRequest(warehouse.Kutaisi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 20, WAREHOUSE.TransportType.AIRPLANE, 1) +-- -- Add assets to Batumi warehouse. +-- warehouse.Batumi:AddAsset("C-130", 1) +-- warehouse.Batumi:AddAsset("TPz Fuchs", 3) +-- +-- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_APC, WAREHOUSE.Quantity.ALL, WAREHOUSE.TransportType.AIRPLANE) -- -- ## Example 7: Capturing Airbase and Warehouse -- @@ -610,45 +655,45 @@ -- -- Here, we simply activate a blue external unit which drives to the warehouse, destroyes the red intruder and re-captures our warehouse. -- --- -- Start warehouse. +-- -- Start warehouses. -- warehouse.Senaki:Start() +-- warehouse.Sukhumi:Start() -- -- -- Add some assets. -- warehouse.Senaki:AddAsset("TPz Fuchs", 5) -- warehouse.Senaki:AddAsset("Infantry Platoon Alpha", 10) -- warehouse.Senaki:AddAsset("F/A-18C 2ship", 10) -- --- -- Auto defence! When enabled, all ground troops of the warehouse are spawned automatically to defend the warehouse. --- -- warehouse.Senaki:SetAutoDefenceOn() --- --- -- Red BMP trying to capture the airfield and later the warehouse. --- local red1=GROUP:FindByName("Red BMP-80 Senaki") --- red1:Activate() --- --- -- The red BMP first drives to the airbase which gets captured and changes from blue to red. So the warehouse loses its airbase. --- function warehouse.Senaki:OnAfterAirbaseCaptured(From,Event,To,Coalition) --- -- This request should not be processed since the warehouse has lost its airbase. In fact it is deleted from the queue. +-- -- Enable auto defence, i.e. spawn all group troups into the spawn zone. +-- --warehouse.Senaki:SetAutoDefenceOn() +-- +-- -- Activate Red BMP trying to capture the airfield and the warehouse. +-- local red1=GROUP:FindByName("Red BMP-80 Senaki"):Activate() +-- +-- -- The red BMP first drives to the airbase which gets captured and changes from blue to red. +-- -- This triggers the "AirbaseCaptured" event where you can hook in and do things. +-- function warehouse.Senaki:OnAfterAirbaseCaptured(From, Event, To, Coalition) +-- -- This request cannot be processed since the warehouse has lost its airbase. In fact it is deleted from the queue. -- warehouse.Senaki:AddRequest(warehouse.Senaki,WAREHOUSE.Descriptor.CATEGORY, Group.Category.AIRPLANE, 1) -- end -- --- -- Enemy has entered the warehouse zone. This triggers the "Attacked" event. --- function warehouse.Senaki:OnAfterAttacked(From,Event,To,Coalition,Country) --- MESSAGE:New(string.format("Warehouse %s: We are under attack!", self.alias), 30):ToCoalition(self:GetCoalition()) --- self:GetCoordinate():SmokeRed() --- end --- --- -- Now the red BMP also captured the warehouse. So the warehouse and the airbase are both red and planes can be spawned again. --- function warehouse.Senaki:OnAfterCaptured(From,Event,To,Coalition,Country) +-- -- Now the red BMP also captures the warehouse. This triggers the "Captured" event where you can hook in. +-- -- So now the warehouse and the airbase are both red and aircraft can be spawned again. +-- function warehouse.Senaki:OnAfterCaptured(From, Event, To, Coalition, Country) -- -- These units will be spawned as red units because the warehouse has just been captured. --- warehouse.Senaki:AddRequest(warehouse.Senaki,WAREHOUSE.Descriptor.CATEGORY, Group.Category.AIRPLANE, 1) +-- if Coalition==coalition.side.RED then +-- -- Sukhumi tries to "steals" three F/A-18 from Senaki and brings them to Sukhumi. +-- -- Well, actually the aircraft wont make it because blue1 will kill it on the taxi way leaving a blood bath. But that's life! +-- warehouse.Senaki:AddRequest(warehouse.Sukhumi, WAREHOUSE.Descriptor.CATEGORY, Group.Category.AIRPLANE, 3) +-- end -- --- -- Activate Blue Humvee to recapture the warehouse. --- local blue1=GROUP:FindByName("blue1") --- blue1:Activate() +-- -- Activate a blue vehicle to re-capture the warehouse. It will drive to the warehouse zone and kill the red intruder. +-- local blue1=GROUP:FindByName("blue1"):Activate() -- end -- -- ## Example 8: Destroying a Warehouse -- +-- FARP Berlin requests a Huey from Batumi warehouse. This helo is deployed and will be delivered. -- After 30 seconds into the mission we create and (artificial) big explosion - or a terrorist attack if you like - which completely destroys the -- the warehouse at Batumi. All assets are gone and requests cannot be processed anymore. -- @@ -660,47 +705,103 @@ -- warehouse.Batumi:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) -- warehouse.Berlin:AddAsset("Huey", 5, WAREHOUSE.Attribute.AIR_TRANSPORTHELO) -- --- -- Big explosion at the warehouse. It has a very nice damage model by the way :) +-- -- Big explosion at the warehose. It has a very nice damage model by the way :) -- local function DestroyWarehouse() --- warehouse.Batumi.warehouse:GetCoordinate():Explosion(9999) +-- warehouse.Batumi.warehouse:GetCoordinate():Explosion(999) -- end --- --- -- Create an explosion at the warehouse after 30 sec. -- SCHEDULER:New(nil, DestroyWarehouse, {}, 30) -- --- -- These requests should not be processed any more since the warehouse is destroyed. +-- -- First request is okay since warehouse is still alive. +-- warehouse.Batumi:AddRequest(warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_TRANSPORTHELO, 1) +-- +-- -- These requests should both not be processed any more since the warehouse at Batumi is destroyed. -- warehouse.Batumi:__AddRequest(35, warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_TRANSPORTHELO, 1) -- warehouse.Berlin:__AddRequest(40, warehouse.Batumi, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_TRANSPORTHELO, 1) -- -- ## Example 9: Self Propelled Naval Assets -- --- Kobuleti requests a war ship from Batumi. Both warehouses need to have a port, which we define by two polygon zones at a place --- in the sea closest to the warehouses. Also a shipping lane between the two warehouses needs to be defined manually. --- With this infrastructure it is possible to exachange naval assets between warehouses. +-- Kobuleti requests all naval assets from Batumi. +-- However, before naval assets can be exchanged, both warehouses need a port and at least one shipping lane defined by the user. +-- See the @{#WAREHOUSE.SetPortZone}() and @{#WAREHOUSE.AddShippingLane}() functions. +-- We do not want to spawn them all at once, because this will probably be a disaster +-- in the port zone. Therefore, each ship is spawned with a delay of five minutes. +-- +-- Batumi has quite a selection of different ships (for testing). +-- +-- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Naval_Assets.png) -- -- -- Start warehouses. -- warehouse.Batumi:Start() -- warehouse.Kobuleti:Start() -- --- -- Define ports and shipping lanes. --- warehouse.Batumi:SetPortZone(ZONE_POLYGON:NewFromGroupName("Warehouse Batumi Port", "Warehouse Batumi Port")) --- warehouse.Kobuleti:SetPortZone(ZONE_POLYGON:NewFromGroupName("Warehouse Kobuleti Port", "Warehouse Kobuleti Port")) --- warehouse.Batumi:AddShippingLane(warehouse.Kobuleti, GROUP:FindByName("Warehouse Batumi-Kobuleti Shipping Lane")) +-- -- Define ports. These are polygon zones created by the waypoints of late activated units. +-- warehouse.Batumi:SetPortZone(ZONE_POLYGON:NewFromGroupName("Warehouse Batumi Port Zone", "Warehouse Batumi Port Zone")) +-- warehouse.Kobuleti:SetPortZone(ZONE_POLYGON:NewFromGroupName("Warehouse Kobuleti Port Zone", "Warehouse Kobuleti Port Zone")) -- --- -- Add five USS Normandy naval assets. --- warehouse.Batumi:AddAsset("Normandy", 5) +-- -- Shipping lane. Again, the waypoints of late activated units are taken as points defining the shipping lane. +-- -- Some units will take lane 1 while others will take lane two. But both lead from Batumi to Kobuleti port. +-- warehouse.Batumi:AddShippingLane(warehouse.Kobuleti, GROUP:FindByName("Warehouse Batumi-Kobuleti Shipping Lane 1")) +-- warehouse.Batumi:AddShippingLane(warehouse.Kobuleti, GROUP:FindByName("Warehouse Batumi-Kobuleti Shipping Lane 2")) -- --- -- Kobuleti requests a war ship from Batumi. --- warehouse.Batumi:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.NAVAL_WARSHIP) +-- -- Large selection of available naval units in DCS. +-- warehouse.Batumi:AddAsset("Speedboat") +-- warehouse.Batumi:AddAsset("Perry") +-- warehouse.Batumi:AddAsset("Normandy") +-- warehouse.Batumi:AddAsset("Stennis") +-- warehouse.Batumi:AddAsset("Carl Vinson") +-- warehouse.Batumi:AddAsset("Tarawa") +-- warehouse.Batumi:AddAsset("SSK 877") +-- warehouse.Batumi:AddAsset("SSK 641B") +-- warehouse.Batumi:AddAsset("Grisha") +-- warehouse.Batumi:AddAsset("Molniya") +-- warehouse.Batumi:AddAsset("Neustrashimy") +-- warehouse.Batumi:AddAsset("Rezky") +-- warehouse.Batumi:AddAsset("Moskva") +-- warehouse.Batumi:AddAsset("Pyotr Velikiy") +-- warehouse.Batumi:AddAsset("Kuznetsov") +-- warehouse.Batumi:AddAsset("Zvezdny") +-- warehouse.Batumi:AddAsset("Yakushev") +-- warehouse.Batumi:AddAsset("Elnya") +-- warehouse.Batumi:AddAsset("Ivanov") +-- warehouse.Batumi:AddAsset("Yantai") +-- warehouse.Batumi:AddAsset("Type 052C") +-- warehouse.Batumi:AddAsset("Guangzhou") +-- +-- -- Get Number of ships at Batumi. +-- local nships=warehouse.Batumi:GetNumberOfAssets(WAREHOUSE.Descriptor.CATEGORY, Group.Category.SHIP) +-- +-- -- Send one ship every 5 minutes. +-- for i=1, nships do +-- warehouse.Batumi:__AddRequest(300*(i-1)+10, warehouse.Kobuleti, WAREHOUSE.Descriptor.CATEGORY, Group.Category.SHIP, 1) +-- end +-- +-- ## Example 10: Warehouse on Aircraft Carrier -- --- ## Example 10: Aircraft Carrier - Rescue Helo and Escort --- -- This example shows how to spawn assets from a warehouse located on an aircraft carrier. The warehouse must still be represented by a -- physical static object. However, on a carrier space is limit so we take a smaller static. In priciple one could also take something -- like a windsock. -- -- ![Banner Image](..\Presentations\WAREHOUSE\Warehouse_Carrier.png) -- +-- USS Stennis requests F/A-18s from Batumi. At the same time Kobuleti requests F/A-18s from the Stennis which currently does not have any. +-- So first, Batumi delivers the fighters to the Stennis. After they arrived they are deployed again and send to Kobuleti. +-- +-- -- Start warehouses. +-- warehouse.Batumi:Start() +-- warehouse.Stennis:Start() +-- warehouse.Kobuleti:Start() +-- +-- -- Add F/A-18 2-ship flight to Batmi. +-- warehouse.Batumi:AddAsset("F/A-18C 2ship", 1) +-- +-- -- USS Stennis requests F/A-18 from Batumi. +-- warehouse.Batumi:AddRequest(warehouse.Stennis, WAREHOUSE.Descriptor.TEMPLATENAME, "F/A-18C 2ship") +-- +-- -- Kobuleti requests F/A-18 from USS Stennis. +-- warehouse.Stennis:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "F/A-18C 2ship") +-- +-- ## Example 11: Aircraft Carrier - Rescue Helo and Escort +-- -- After 10 seconds we make a self request for a rescue helicopter. Note, that the @{#WAREHOUSE.AddRequest} function has a parameter which lets you -- specify an "Assignment". This can be later used to identify the request and take the right actions. -- @@ -721,79 +822,291 @@ -- -- Start warehouse on USS Stennis. -- warehouse.Stennis:Start() -- --- -- Add speedboat and helo assets. +-- -- Aircraft carrier gets a moving zone right behind it as port. +-- warehouse.Stennis:SetPortZone(ZONE_UNIT:New("Warehouse Stennis Port Zone", UNIT:FindByName("USS Stennis"), 100, {rho=250, theta=180, relative_to_unit=true})) +-- +-- -- Add speedboat assets. -- warehouse.Stennis:AddAsset("Speedboat", 10) --- warehouse.Stennis:AddAsset("CH-53E", 3) +-- warehouse.Stennis:AddAsset("CH-53E", 1) -- --- -- Define a "port" at the Stennis to be able to spawn Naval assets. This zone will move behind the Stennis. --- local stenniszone=ZONE_UNIT:New("Spawnzone Stennis", UNIT:FindByName("USS Stennis"), 100, {rho=250, theta=180, relative_to_unit=true}) --- warehouse.Stennis:SetPortZone(stenniszone) --- --- -- Self request of rescue helo and speed boats. +-- -- Self request of speed boats. -- warehouse.Stennis:__AddRequest(10, warehouse.Stennis, WAREHOUSE.Descriptor.TEMPLATENAME, "CH-53E", 1, nil, nil, nil, "Rescue Helo") -- warehouse.Stennis:__AddRequest(30, warehouse.Stennis, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.NAVAL_ARMEDSHIP, 5, nil, nil, nil, "Speedboats Left") -- warehouse.Stennis:__AddRequest(45, warehouse.Stennis, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.NAVAL_ARMEDSHIP, 5, nil, nil, nil, "Speedboats Right") -- -- --- Function called after self request --- function warehouse.Stennis:OnAfterSelfRequest(From,Event,To,groupset,request) +-- function warehouse.Stennis:OnAfterSelfRequest(From, Event, To,_groupset, request) -- --- local groupset=groupset --Core.Set#SET_GROUP +-- local groupset=_groupset --Core.Set#SET_GROUP -- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem -- -- -- USS Stennis is the mother ship. -- local Mother=UNIT:FindByName("USS Stennis") -- --- -- Get assignment for this request. +-- -- Get assignment of the request. -- local assignment=warehouse.Stennis:GetAssignment(request) -- -- if assignment=="Speedboats Left" then -- -- -- Define AI Formation object. -- -- Note that this has to be a global variable or the garbage collector will remove it for some reason! --- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Port Formation with Carrier", "Follow Carrier at given parameters.") --- --- -- Formation parameters and start. --- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50) +-- CarrierFormationLeft = AI_FORMATION:New(Mother, groupset, "Left Formation with Carrier", "Escort Carrier.") +-- +-- -- Formation parameters. +-- CarrierFormationLeft:FormationLeftWing(200 ,50, 0, 0, 500, 50) -- CarrierFormationLeft:__Start(2) --- +-- -- for _,group in pairs(groupset:GetSetObjects()) do -- local group=group --Wrapper.Group#GROUP -- group:FlareRed() --- end +-- end -- -- elseif assignment=="Speedboats Right" then -- -- -- Define AI Formation object. -- -- Note that this has to be a global variable or the garbage collector will remove it for some reason! --- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Starboard Formation with Carrier", "Follow Carrier at given parameters.") --- --- -- Formation parameters and start. --- CarrierFormationRight:FormationRightWing(200 ,50, 0, 0, 500, 50) --- CarrierFormationRight:__Start(2) +-- CarrierFormationRight = AI_FORMATION:New(Mother, groupset, "Right Formation with Carrier", "Escort Carrier.") +-- +-- -- Formation parameters. +-- CarrierFormationRight:FormationRightWing(200 ,50, 0, 0, 500, 50) +-- CarrierFormationRight:__Start(2) +-- +-- for _,group in pairs(groupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP +-- group:FlareGreen() +-- end -- -- elseif assignment=="Rescue Helo" then --- +-- +-- -- Start uncontrolled helo. +-- local group=groupset:GetFirst() --Wrapper.Group#GROUP +-- group:StartUncontrolled() +-- -- -- Define AI Formation object. --- CarrierFormationHelo = AI_FORMATION:New(Mother, groupset, "Helo Formation with Carrier", "Follow Carrier at given parameters.") --- --- -- Formation parameters and start. +-- CarrierFormationHelo = AI_FORMATION:New(Mother, groupset, "Helo Formation with Carrier", "Fly Formation.") +-- +-- -- Formation parameters. -- CarrierFormationHelo:FormationCenterWing(-150, 50, 20, 50, 100, 50) -- CarrierFormationHelo:__Start(2) -- -- end -- --- --- When the helo is out of fuel, it will return to the carrier. The asset is considered as delivered. +-- --- When the helo is out of fuel, it will return to the carrier and should be delivered. -- function warehouse.Stennis:OnAfterDelivered(From,Event,To,request) -- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem -- -- -- So we start another request. --- if warehouse.Stennis:GetAssignment(request)=="Rescue Helo" then +-- if request.assignment=="Rescue Helo" then -- warehouse.Stennis:__AddRequest(10, warehouse.Stennis, WAREHOUSE.Descriptor.TEMPLATENAME, "CH-53E", 1, nil, nil, nil, "Rescue Helo") -- end -- end -- -- end -- +-- ## Example 12: Pause and Unpause a Warehouse +-- +-- This example shows how to pause a warehouse. In paused state, no requests will be processed but assets can be added or be requests made. +-- +-- * Warehouse Batumi is paused after 10 seconds. +-- * Request from Berlin after 15 which will not be processed. +-- * New tank assets for Batumi after 20 seconds. This is possible also in paused state. +-- * Batumi unpaused after 30 seconds. Queued request from Berlin can be processed. +-- * Berlin is paused after 60 seconds. +-- * Berlin requests tanks from Batumi after 90 seconds. Request is not processed because Berlin is paused and not running. +-- * Berlin is unpaused after 120 seconds. Queued request for tanks from Batumi can not be processed. +-- +-- Here is the code: +-- +-- -- Start Warehouse at Batumi. +-- warehouse.Batumi:Start() +-- +-- -- Start Warehouse Berlin. +-- warehouse.Berlin:Start() +-- +-- -- Add 20 infantry groups and 5 tank platoons as assets at Batumi. +-- warehouse.Batumi:AddAsset("Infantry Platoon Alpha", 20) +-- +-- -- Pause the warehouse after 10 seconds +-- warehouse.Batumi:__Pause(10) +-- +-- -- Add a request from Berlin after 15 seconds. A request can be added but not be processed while warehouse is paused. +-- warehouse.Batumi:__AddRequest(15, warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_INFANTRY, 1) +-- +-- -- New asset added after 20 seconds. This is possible even if the warehouse is paused. +-- warehouse.Batumi:__AddAsset(20, "Abrams", 5) +-- +-- -- Unpause warehouse after 30 seconds. Now the request from Berlin can be processed. +-- warehouse.Batumi:__Unpause(30) +-- +-- -- Pause warehouse Berlin +-- warehouse.Berlin:__Pause(60) +-- +-- -- After 90 seconds request from Berlin for tanks. +-- warehouse.Batumi:__AddRequest(90, warehouse.Berlin, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_TANK, 1) +-- +-- -- After 120 seconds unpause Berlin. +-- warehouse.Berlin:__Unpause(120) +-- +-- ## Example 13: Battlefield Air Interdiction +-- +-- This example show how to couple the WAREHOUSE class with the @{AI.AI_BAI} class. +-- Four enemy targets have been located at the famous Kobuleti X. Three Viggen 2-ship flights are assigned to kill at least one of the BMPs to complete their mission. +-- +-- -- Start Warehouse at Kobuleti. +-- warehouse.Kobuleti:Start() +-- +-- -- Add three 2-ship groups of Viggens. +-- warehouse.Kobuleti:AddAsset("Viggen 2ship", 3) +-- +-- -- Self request for all Viggen assets. +-- warehouse.Kobuleti:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.TEMPLATENAME, "Viggen 2ship", WAREHOUSE.Quantity.ALL, nil, nil, nil, "BAI") +-- +-- -- Red targets at Kobuleti X (late activated). +-- local RedTargets=GROUP:FindByName("Red IVF Alpha") +-- +-- -- Activate the targets. +-- RedTargets:Activate() +-- +-- -- Do something with the spawned aircraft. +-- function warehouse.Kobuleti:OnAfterSelfRequest(From,Event,To,groupset,request) +-- local groupset=groupset --Core.Set#SET_GROUP +-- local request=request --Functional.Warehouse#WAREHOUSE.Pendingitem +-- +-- if request.assignment=="BAI" then +-- +-- for _,group in pairs(groupset:GetSetObjects()) do +-- local group=group --Wrapper.Group#GROUP +-- +-- -- Start uncontrolled aircraft. +-- group:StartUncontrolled() +-- +-- local BAI=AI_BAI_ZONE:New(ZONE:New("Patrol Zone Kobuleti"), 500, 1000, 500, 600, ZONE:New("Patrol Zone Kobuleti")) +-- +-- -- Tell the program to use the object (in this case called BAIPlane) as the group to use in the BAI function +-- BAI:SetControllable(group) +-- +-- -- Function checking if targets are still alive +-- local function CheckTargets() +-- local nTargets=RedTargets:GetSize() +-- local nInitial=RedTargets:GetInitialSize() +-- local nDead=nInitial-nTargets +-- local nRequired=1 -- Let's make this easy. +-- if RedTargets:IsAlive() and nDead < nRequired then +-- MESSAGE:New(string.format("BAI Mission: %d of %d red targets still alive. At least %d targets need to be eliminated.", nTargets, nInitial, nRequired), 5):ToAll() +-- else +-- MESSAGE:New("BAI Mission: The required red targets are destroyed.", 30):ToAll() +-- BAI:__Accomplish(1) -- Now they should fly back to the patrolzone and patrol. +-- end +-- end +-- +-- -- Start scheduler to monitor number of targets. +-- local Check, CheckScheduleID = SCHEDULER:New(nil, CheckTargets, {}, 60, 60) +-- +-- -- When the targets in the zone are destroyed, (see scheduled function), the planes will return home ... +-- function BAI:OnAfterAccomplish( Controllable, From, Event, To ) +-- MESSAGE:New( "BAI Mission: Sending the Viggens back to base.", 30):ToAll() +-- Check:Stop(CheckScheduleID) +-- BAI:__RTB(1) +-- end +-- +-- -- Start BAI +-- BAI:Start() +-- +-- -- Engage after 5 minutes. +-- BAI:__Engage(300) +-- +-- -- RTB after 30 min max. +-- BAI:__RTB(-30*60) +-- +-- end +-- end +-- +-- end +-- +-- ## Example 14: Strategic Bombing +-- +-- This example shows how to employ stategic bombers in a mission. Three B-52s are lauched at Kobuleti with the assignment to wipe out the enemy warehouse at Sukhumi. +-- The bombers will get a flight path and make their approach from the South at an altitude of 5000 m ASL. After their bombing run, they will return to Kobuleti and +-- added back to stock. +-- +-- -- Start warehouses +-- warehouse.Kobuleti:Start() +-- warehouse.Sukhumi:Start() +-- +-- -- Add a strategic bomber assets +-- warehouse.Kobuleti:AddAsset("B-52H", 3) +-- +-- -- Request bombers for specific task of bombing Sukhumi warehouse. +-- warehouse.Kobuleti:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.AIR_BOMBER, WAREHOUSE.Quantity.ALL, nil, nil, nil, "Bomb Sukhumi") +-- +-- -- Specify assignment after bombers have been spawned. +-- function warehouse.Kobuleti:OnAfterSelfRequest(From, Event, To, groupset, request) +-- local groupset=groupset --Core.Set#SET_GROUP +-- +-- -- Get assignment of this request. +-- local assignment=warehouse.Kobuleti:GetAssignment(request) +-- +-- if assignment=="Bomb Sukhumi" then +-- +-- for _,_group in pairs(groupset:GetSet()) do +-- local group=_group --Wrapper.Group#GROUP +-- +-- group:StartUncontrolled() +-- group:SmokeBlue() +-- +-- -- Target coordinate! +-- local ToCoord=warehouse.Sukhumi:GetCoordinate() +-- ToCoord.y=5000 -- Adjust altitude +-- +-- local FoCoord=warehouse.Kobuleti:GetCoordinate() +-- FoCoord.y=3000 -- Ajust altitude. +-- +-- -- Task bomb Sukhumi warehouse using all bombs (2032) from direction 180 at altitude 5000 m. +-- local task=group:TaskBombing(warehouse.Sukhumi:GetCoordinate():GetVec2(), false, "All", nil , 180, 5000, 2032) +-- +-- -- Define waypoints. +-- local WayPoints={} +-- +-- -- Take off position. +-- WayPoints[1]=warehouse.Kobuleti:GetCoordinate():WaypointAirTakeOffParking() +-- -- Begin bombing run 20 km south of target. +-- WayPoints[2]=ToCoord:Translate(20*1000, 180):WaypointAirTurningPoint(nil, 600, {task}, "Bombing Run") +-- -- Return to base. +-- WayPoints[3]=FoCoord:WaypointAirTurningPoint() +-- -- Land at homebase. Bombers are added back to stock and can be employed in later assignments. +-- WayPoints[4]=warehouse.Kobuleti:GetCoordinate():WaypointAirLanding() +-- +-- -- Route bombers. +-- group:Route(WayPoints) +-- end +-- +-- end +-- end +-- +-- ## Example 15: Defining Off-Road Paths +-- +-- For self propelled assets it is possible to define custom off-road paths from one warehouse to another via the @{#WAREHOUSE.AddOffRoadPath} function. +-- The waypoints of a path are taken from late activated units. In this example, two paths have been defined between the warehouses Kobuleti and FARP London. +-- Trucks are spawned at each warehouse and are guided along the paths to the other warehouse. +-- Note that if more than one path was defined, each asset group will randomly select its route. +-- +-- -- Start warehouses +-- warehouse.Kobuleti:Start() +-- warehouse.London:Start() +-- +-- warehouse.Kobuleti:AddAsset("M978", 20) +-- warehouse.London:AddAsset("M818", 20) +-- +-- -- Off two road paths from Kobuleti to London. The reverse path from London to Kobuleti is added automatically. +-- warehouse.Kobuleti:AddOffRoadPath(warehouse.London, GROUP:FindByName("Warehouse Kobuleti-London OffRoad Path 1")) +-- warehouse.Kobuleti:AddOffRoadPath(warehouse.London, GROUP:FindByName("Warehouse Kobuleti-London OffRoad Path 2")) +-- +-- -- London requests all available trucks from Kobuleti. +-- warehouse.Kobuleti:AddRequest(warehouse.London, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_TRUCK, WAREHOUSE.Quantity.ALL) +-- +-- -- Kobuleti requests all available trucks from London. +-- warehouse.London:AddRequest(warehouse.Kobuleti, WAREHOUSE.Descriptor.ATTRIBUTE, WAREHOUSE.Attribute.GROUND_TRUCK, WAREHOUSE.Quantity.HALF) +-- -- -- @field #WAREHOUSE WAREHOUSE = { @@ -990,13 +1303,12 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.4.9" +WAREHOUSE.version="0.5.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Get cargo bay and weight from CARGO_GROUP and GROUP. --- TODO: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters. + -- TODO: Spawn assets only virtually, i.e. remove requested assets from stock but do NOT spawn them ==> Interface to A2A dispatcher! Maybe do a negative sign on asset number? -- TODO: Test capturing a neutral warehouse. -- TODO: Make more examples: ARTY, CAP, ... @@ -1004,6 +1316,8 @@ WAREHOUSE.version="0.4.9" -- TODO: Handle the case when units of a group die during the transfer. -- TODO: Added habours as interface for transport to from warehouses? -- TODO: Add save/load capability of warehouse <==> percistance after mission restart. Difficult in lua! +-- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more! +-- DONE: Add possibility to set weight and cargo bay manually in AddAsset function as optional parameters. -- DONE: Check overlapping aircraft sometimes. -- DONE: Case when all transports are killed and there is still cargo to be delivered. Put cargo back into warehouse. Should be done now! -- DONE: Add transport units from dispatchers back to warehouse stock once they completed their mission. @@ -1027,7 +1341,7 @@ WAREHOUSE.version="0.4.9" -- DONE: Add general message function for sending to coaliton or debug. -- DONE: Fine tune event handlers. -- DONE: Improve generalized attributes. --- DONE: If warehouse is destoyed, all asssets are gone. +-- DONE: If warehouse is destroyed, all asssets are gone. -- DONE: Add event handlers. -- DONE: Add AI_CARGO_AIRPLANE -- DONE: Add AI_CARGO_APC @@ -1125,7 +1439,7 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("Attacked", "Captured", "Running") -- Warehouse was captured by another coalition. It must have been attacked first. self:AddTransition("*", "AirbaseCaptured", "*") -- Airbase was captured by other coalition. self:AddTransition("*", "AirbaseRecaptured", "*") -- Airbase was re-captured from other coalition. - self:AddTransition("*", "Destroyed", "Destoyed") -- Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("*", "Destroyed", "Destroyed") -- Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. ------------------------ --- Pseudo Functions --- @@ -2171,10 +2485,6 @@ function WAREHOUSE:onafterStatus(From, Event, To) -- Check if warehouse is being attacked or has even been captured. self:_CheckConquered() - - -- Print queue. - --self:_PrintQueue(self.queue, "Queue waiting - before request") - --self:_PrintQueue(self.pending, "Queue pending - before request") -- Check if requests are valid and remove invalid one. self:_CheckRequestConsistancy(self.queue) @@ -2189,13 +2499,13 @@ function WAREHOUSE:onafterStatus(From, Event, To) if request then self:Request(request) end - - -- Print queue after processing requests. - self:_PrintQueue(self.queue, "Queue waiting") - self:_PrintQueue(self.pending, "Queue pending") - + end + -- Print queue after processing requests. + self:_PrintQueue(self.queue, "Queue waiting") + self:_PrintQueue(self.pending, "Queue pending") + -- Update warhouse marker on F10 map. self:_UpdateWarehouseMarkText() @@ -2461,7 +2771,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu ------------------------- -- Debug info. - self:_DebugMessage(self.wid..string.format("Warehouse %s: Adding %d NEW assets of group %s to stock.", self.alias, n, tostring(group:GetName())), 5) + self:_DebugMessage(string.format("Warehouse %s: Adding %d NEW assets of group %s to stock.", self.alias, n, tostring(group:GetName())), 5) -- This is a group that is not in the db yet. Add it n times. local assets=self:_RegisterAsset(group, n, forceattribute, forcecargobay, forceweight) @@ -2696,7 +3006,19 @@ function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescripto self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, TEMPLATENAME or UNITTYPE!", 5) okay=false end - + + -- Warehouse is stopped? + if self:IsStopped() then + self:_ErrorMessage("ERROR: Invalid request. Warehouse is stopped!", 0) + okay=false + end + + -- Warehouse is destroyed? + if self:IsDestroyed() then + self:_ErrorMessage("ERROR: Invalid request. Warehouse is destroyed!", 0) + okay=false + end + return okay end @@ -2758,7 +3080,7 @@ function WAREHOUSE:onafterAddRequest(From, Event, To, warehouse, AssetDescriptor -- Add request to queue. table.insert(self.queue, request) - local text=string.format("Warehouse %s: New request from %s. Descriptor %s=%s, #assets=%s; Transport=%s, #transports =%s.", + local text=string.format("Warehouse %s: New request from warehouse %s.\nDescriptor %s=%s, #assets=%s; Transport=%s, #transports =%s.", self.alias, warehouse.alias, request.assetdesc, tostring(request.assetdescval), tostring(request.nasset), request.transporttype, tostring(request.ntransport)) self:_DebugMessage(text, 5) @@ -3633,12 +3955,23 @@ end function WAREHOUSE:onafterDestroyed(From, Event, To) -- Message. - local text=string.format("Warehouse %s was destroyed!", self.alias) + local text=string.format("Warehouse %s was destroyed! Assets lost %d.", self.alias, #self.stock) self:_InfoMessage(text) + + -- Remove all table entries from waiting queue and stock. + for k,_ in pairs(self.queue) do + self.queue[k]=nil + end + for k,_ in pairs(self.stock) do + self.stock[k]=nil + end + + --self.queue=nil + --self.queue={} + + --self.stock=nil + --self.stock={} - -- Stop warehouse FSM in one minute. - -- Maybe dont stop it or pending requests are not updated any more. - --self:__Stop(60) end --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3709,7 +4042,6 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Spawn train. if self.rail then --TODO: Rail should only get one asset because they would spawn on top! - --_group=_spawn:SpawnFromCoordinate(self.rail) end self:E(self.wid.."ERROR: Spawning of TRAIN assets not possible yet!") @@ -3858,6 +4190,7 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol -- Check enough parking spots. if AirbaseCategory==Airbase.Category.HELIPAD or AirbaseCategory==Airbase.Category.SHIP then + --TODO Figure out what's necessary in this case. else @@ -3913,9 +4246,10 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol -- DCS bug workaround. Spawning helos in uncontrolled state on carriers causes a big spash! -- See https://forums.eagle.ru/showthread.php?t=219550 - if AirbaseCategory == Airbase.Category.SHIP and asset.category==Group.Category.HELICOPTER then - uncontrolled=false - end + -- Should be solved in latest OB update 2.5.3.21708 + --if AirbaseCategory == Airbase.Category.SHIP and asset.category==Group.Category.HELICOPTER then + -- uncontrolled=false + --end -- Uncontrolled spawning. template.uncontrolled=uncontrolled @@ -4729,6 +5063,12 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is stopped!")) valid=false end + + -- Is receiving warehouse destroyed? + if request.warehouse:IsDestroyed() then + self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!")) + valid=false + end -- Add request as unvalid and delete it later. if valid==false then @@ -6171,7 +6511,7 @@ function WAREHOUSE:_UpdateWarehouseMarkText() local _data=self:GetStockInfo(self.stock) -- Text. - local text=string.format("Warehouse Stock - total assets %d:\n", #self.stock) + local text=string.format("Warehouse state: %s\nStock - total assets %d:\n", self:GetState(), #self.stock) for _attribute,_count in pairs(_data) do if _count>0 then @@ -6531,7 +6871,7 @@ function WAREHOUSE:_GetFlightplan(asset, departure, destination) text=text..string.format("FL max = %.3f km\n", FLmax/1000) text=text..string.format("Ceiling = %.3f km\n", ceiling/1000) text=text..string.format("Max range = %.3f km\n", Range/1000) - env.info(text) + self:T(self.wid..text) -- Ensure that cruise distance is positve. Can be slightly negative in special cases. And we don't want to turn back. if d_cruise<0 then From ab5f08019134bbf598fb0604eea446f4c8fec763 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 18 Sep 2018 11:39:36 +0200 Subject: [PATCH 71/73] Warehouse self:I --> self:T --- Moose Development/Moose/Functional/Warehouse.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index cdfb5c6ec..eed4d3424 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -3199,7 +3199,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- No transport unit requested. Assets go by themselfes. if Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then - self:I(self.wid..string.format("Got selfpropelled request for %d assets.",_spawngroups:Count())) + self:T2(self.wid..string.format("Got selfpropelled request for %d assets.",_spawngroups:Count())) for _,_spawngroup in pairs(_spawngroups:GetSetObjects()) do @@ -3209,7 +3209,7 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Route cargo to their destination. if _cargocategory==Group.Category.GROUND then - self:I(self.wid..string.format("Route ground group %s.", group:GetName())) + self:T2(self.wid..string.format("Route ground group %s.", group:GetName())) -- Random place in the spawn zone of the requesting warehouse. local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate() @@ -3219,20 +3219,20 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) self:_RouteGround(group, Request) elseif _cargocategory==Group.Category.AIRPLANE or _cargocategory==Group.Category.HELICOPTER then - self:I(self.wid..string.format("Route airborne group %s.", group:GetName())) + self:T2(self.wid..string.format("Route airborne group %s.", group:GetName())) -- Route plane to the requesting warehouses airbase. -- Actually, the route is already set. We only need to activate the uncontrolled group. self:_RouteAir(group, Request.airbase) elseif _cargocategory==Group.Category.SHIP then - self:I(self.wid..string.format("Route naval group %s.", group:GetName())) + self:T2(self.wid..string.format("Route naval group %s.", group:GetName())) -- Route plane to the requesting warehouses airbase. self:_RouteNaval(group, Request) elseif _cargocategory==Group.Category.TRAIN then - self:I(self.wid..string.format("Route train group %s.", group:GetName())) + self:T2(self.wid..string.format("Route train group %s.", group:GetName())) -- Route train to the rail connection of the requesting warehouse. self:_RouteTrain(group, Request.warehouse.rail) From bab1d21cc0fb6fa960d7b0a46f5cea4046b192f3 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 18 Sep 2018 16:18:40 +0200 Subject: [PATCH 72/73] Warehouse v0.5.1 Final polish. --- Moose Development/Moose/Core/Spawn.lua | 2 +- Moose Development/Moose/Core/SpawnStatic.lua | 6 ++-- Moose Development/Moose/Core/Zone.lua | 2 ++ Moose Development/Moose/Functional/RAT.lua | 2 +- .../Moose/Functional/Warehouse.lua | 32 +++++++++++++------ 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 614422720..9437c4509 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1615,7 +1615,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT else - self:E(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + self:T(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) -- Get coordinates of parking spot. SpawnTemplate.units[UnitID].x = parkingspots[UnitID].x diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index c65f91ef2..742bf6eac 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -191,16 +191,14 @@ end --- Respawns the original @{Static}. -- @param #SPAWNSTATIC self --- @param DCS#country.id (Optional) The country ID of the static after respawning.. -- @return #SPAWNSTATIC -function SPAWNSTATIC:ReSpawn(countryid) +function SPAWNSTATIC:ReSpawn() local StaticTemplate, CoalitionID, CategoryID, CountryID = _DATABASE:GetStaticGroupTemplate( self.SpawnTemplatePrefix ) if StaticTemplate then - --local CountryID = countryid or (self.CountryID or CountryID) - + local StaticUnitTemplate = StaticTemplate.units[1] StaticTemplate.route = nil StaticTemplate.groupId = nil diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 295adabb8..b434295f0 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -312,6 +312,7 @@ end -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. function ZONE_BASE:SmokeZone( SmokeColor ) self:F2( SmokeColor ) + end --- Set the randomization probability of a zone to be selected. @@ -330,6 +331,7 @@ end -- @return #number A value between 0 and 1. 0 = 0% and 1 = 100% probability. function ZONE_BASE:GetZoneProbability() self:F2() + return self.ZoneProbability end diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 75bfe545d..7b9213bde 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -549,7 +549,7 @@ RAT.id="RAT | " --- RAT version. -- @list version RAT.version={ - version = "2.3.3", + version = "2.3.4", print = true, } diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index eed4d3424..ae96b68a0 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -640,7 +640,7 @@ -- -- A red BMP has made it through our defence lines and drives towards our unprotected airbase at Senaki. -- Once the BMP captures the airbase (DCS S\_EVENT_\BASE_\CAPTURED is evaluated) the warehouse at Senaki lost its air infrastructure and it is not --- possible any more to spawn airborne units. All requests for airborne units are rejected and not queued in this case. +-- possible any more to spawn airborne units. All requests for airborne units are rejected and cancelled in this case. -- -- The red BMP then drives further to the warehouse. Once it enters the warehouse zone (500 m radius around the warehouse building), the warehouse is -- considered to be under attack. This triggers the event **Attacked**. The @{#WAREHOUSE.OnAfterAttacked} function can be used to react to this situation. @@ -649,11 +649,10 @@ -- *all* ground assets are automatically spawned and assigned to defend the warehouse. Once/if the attack is defeated, these assets go automatically back -- into the warehouse stock. -- --- If the red coalition manages to capture our warehouse, all assets go into their possession. Here, even our airbase has been captured. Therefore, a (self) request --- to the warehouse will now spawn the F/A-18 fighters as red units. Note, that the request could also some from another red warehouse. In that case, --- the planes would take off and (if they make it) be added to the red warehouse. So you can steal valuable assets from your enemy if he is not careful. +-- If the red coalition manages to capture our warehouse, all assets go into their possession. Now red tries to steal three F/A-18 flights and send them to +-- Sukhumi. These aircraft will be spawned and begin to taxi. However, ... -- --- Here, we simply activate a blue external unit which drives to the warehouse, destroyes the red intruder and re-captures our warehouse. +-- A blue Bradley is in the area and will attemt to recapture the warehouse. It might also catch the red F/A-18s before they take off. -- -- -- Start warehouses. -- warehouse.Senaki:Start() @@ -685,6 +684,9 @@ -- -- Sukhumi tries to "steals" three F/A-18 from Senaki and brings them to Sukhumi. -- -- Well, actually the aircraft wont make it because blue1 will kill it on the taxi way leaving a blood bath. But that's life! -- warehouse.Senaki:AddRequest(warehouse.Sukhumi, WAREHOUSE.Descriptor.CATEGORY, Group.Category.AIRPLANE, 3) +-- warehouse.Senaki.warehouse:SmokeRed() +-- elseif Coalition==coalition.side.BLUE then +-- warehouse.Senaki.warehouse:SmokeBlue() -- end -- -- -- Activate a blue vehicle to re-capture the warehouse. It will drive to the warehouse zone and kill the red intruder. @@ -1306,7 +1308,7 @@ WAREHOUSE.db = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.5.0" +WAREHOUSE.version="0.5.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -3145,6 +3147,12 @@ end -- @param #WAREHOUSE.Queueitem Request Information table of the request. function WAREHOUSE:onafterRequest(From, Event, To, Request) + -- Info message. + local text=string.format("Warehouse %s: Processing request id=%d from warehouse %s.\n", self.alias, Request.uid, Request.warehouse.alias) + text=text..string.format("Requested %s assets of %s=%s.\n", tostring(Request.nasset), Request.assetdesc, Request.assetdescval) + text=text..string.format("Transports %s of type %s.", tostring(Request.ntransport), tostring(Request.transporttype)) + self:_InfoMessage(text, 5) + ------------------------------------------------------------------------------------------------------------------------------------ -- Cargo assets. ------------------------------------------------------------------------------------------------------------------------------------ @@ -3213,7 +3221,11 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) -- Random place in the spawn zone of the requesting warehouse. local ToCoordinate=Request.warehouse.spawnzone:GetRandomCoordinate() - ToCoordinate:MarkToAll(string.format("Destination of group %s", group:GetName())) + + -- Debug marker. + if self.Debug then + ToCoordinate:MarkToAll(string.format("Destination of group %s", group:GetName())) + end -- Route ground. self:_RouteGround(group, Request) @@ -5402,8 +5414,8 @@ function WAREHOUSE:_CheckRequestNow(request) if not _enough then local text=string.format("Warehouse %s: Request denied! Not enough (cargo) assets currently available.", self.alias) self:_InfoMessage(text, 5) - text=string.format("Enough=%s, #_assets=%d, _nassets=%d, request.nasset=%s", tostring(_enough), #_assets,_nassets, tostring(request.nasset)) - env.info(text) + --text=string.format("Enough=%s, #_assets=%d, _nassets=%d, request.nasset=%s", tostring(_enough), #_assets,_nassets, tostring(request.nasset)) + --env.info(text) return false end @@ -6514,7 +6526,7 @@ function WAREHOUSE:_UpdateWarehouseMarkText() local _data=self:GetStockInfo(self.stock) -- Text. - local text=string.format("Warehouse state: %s\nStock - total assets %d:\n", self:GetState(), #self.stock) + local text=string.format("Warehouse state: %s\nTotal assets in stock %d:\n", self:GetState(), #self.stock) for _attribute,_count in pairs(_data) do if _count>0 then From 35e41570dfef18aef53ae48b10e325c31ddbaf83 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Tue, 18 Sep 2018 16:41:17 +0200 Subject: [PATCH 73/73] Spawn and Group Removed debug markers. --- Moose Development/Moose/Core/Spawn.lua | 2 +- Moose Development/Moose/Wrapper/Group.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 9437c4509..8ba3908ca 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1622,7 +1622,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.units[UnitID].y = parkingspots[UnitID].z SpawnTemplate.units[UnitID].alt = parkingspots[UnitID].y - parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) + --parkingspots[UnitID]:MarkToAll(string.format("Group %s spawning at airbase %s on parking spot id %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), parkingindex[UnitID])) end else diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 85894c489..d12f613d0 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -1507,7 +1507,7 @@ function GROUP:RespawnAtCurrentAirbase(SpawnTemplate, Takeoff, Uncontrolled) -- -- Get unit coordinates for respawning position. local uc=unit:GetCoordinate() - uc:MarkToAll(string.format("re-spawnplace %s terminal %d", unit:GetName(), TermialID)) + --uc:MarkToAll(string.format("re-spawnplace %s terminal %d", unit:GetName(), TermialID)) SpawnTemplate.units[UnitID].x = uc.x --Parkingspot.x SpawnTemplate.units[UnitID].y = uc.z --Parkingspot.z