diff --git a/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua new file mode 100644 index 000000000..2de56b9ec --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Dispatcher_Ship.lua @@ -0,0 +1,194 @@ +--- **AI** -- (2.5.1) - Models the intelligent transportation of infantry and other cargo using Ships +-- +-- ## Features: +-- +-- * Transport cargo to various deploy zones using naval vehicles. +-- * Various @{Cargo.Cargo#CARGO} types can be transported, including infantry, vehicles, and crates. +-- * Define a deploy zone of various types to determine the destination of the cargo. +-- * Ships will follow shipping lanes as defined in the Mission Editor. +-- * Multiple ships can transport multiple cargo as a single group. +-- +-- === +-- +-- ## Test Missions: +-- +-- NEED TO DO +-- +-- === +-- +-- ### Author: **acrojason** (derived from AI_Cargo_Dispatcher_APC by FlightControl) +-- +-- === +-- +-- @module AI.AI_Cargo_Dispatcher_Ship +-- @image AI_Cargo_Dispatching_For_Ship.JPG + +--- @type AI_CARGO_DISPATCHER_SHIP +-- @extends AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER + + +--- A dynamic cargo transportation capability for AI groups. +-- +-- Naval vessels can be mobilized to semi-intelligently transport cargo within the simulation. +-- +-- The AI_CARGO_DISPATCHER_SHIP module is derived from the AI_CARGO_DISPATCHER module. +-- +-- ## Note! In order to fully understand the mechanisms of the AI_CARGO_DISPATCHER_SHIP class, it is recommended that you first consult and READ the documentation of the @{AI.AI_Cargo_Dispatcher} module!!! +-- +-- This will be particularly helpful in order to determine how to **Tailor the different cargo handling events**. +-- +-- The AI_CARGO_DISPATCHER_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framwork. +-- 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 generally be declared within the mission to make the AI_CARGO_DISPATCHER_SHIP object recognize the cargo. +-- +-- +-- # 1) AI_CARGO_DISPATCHER_SHIP constructor. +-- +-- * @{AI_CARGO_DISPATCHER_SHIP.New}(): Creates a new AI_CARGO_DISPATCHER_SHIP object. +-- +-- --- +-- +-- # 2) AI_CARGO_DISPATCHER_SHIP is a Finite State Machine. +-- +-- 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. +-- +-- 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. +-- +-- * **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. +-- +-- ## 2.3) Enhance your mission scripts with **Tailored** Event Handling! +-- +-- Within your mission, you can capture these events when triggered, and tailor the events with your own code! +-- Check out the @{AI.AI_Cargo_Dispatcher#AI_CARGO_DISPATCHER} class at chapter 3 for details on the different event handlers that are available and how to use them. +-- +-- **There are a lot of templates available that allows you to quickly setup an event handler for a specific event type!** +-- +-- --- +-- +-- # 3) Set the pickup parameters. +-- +-- Several parameters can be set to pickup cargo: +-- +-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupRadius}(): Sets or randomizes the pickup location for the Ship around the cargo coordinate in a radius defined an outer and optional inner radius. +-- * @{#AI_CARGO_DISPATCHER_SHIP.SetPickupSpeed}(): Set the speed or randomizes the speed in km/h to pickup the cargo. +-- +-- # 4) Set the deploy parameters. +-- +-- Several parameters can be set to deploy cargo: +-- +-- * @{#AI_CARGO_DISPATCHER_SHIP.SetDeployRadius}(): Sets or randomizes the deploy location for the Ship around the cargo coordinate in a radius defined an outer and an optional inner radius. +-- * @{#AI_CARGO_DISPATCHER_SHIP.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. +-- +-- A home zone can be specified to where the Ship will move when there isn't any cargo left for pickup. +-- Use @{#AI_CARGO_DISPATCHER_SHIP.SetHomeZone}() to specify the home zone. +-- +-- If no home zone is specified, the Ship will wait near the deploy zone for a new pickup command. +-- +-- === +-- + +-- @field #AI_CARGO_DISPATCHER_SHIP +AI_CARGO_DISPATCHER_SHIP = { + ClassName = "AI_CARGO_DISPATCHER_SHIP" + } + +--- Creates a new AI_CARGO_DISPATCHER_SHIP object. +-- @param #AI_CARGO_DISPATCHER_SHIP self +-- @param Core.Set#SET_GROUP ShipSet The set of @{Wrapper.Group#GROUP} objects of Ships that will transport the cargo +-- @param Core.Set#SET_CARGO CargoSet The set of @{Cargo.Cargo#CARGO} objects, which can be CARGO_GROUP, CARGO_CRATE, or CARGO_SLINGLOAD objects. +-- @param Core.Set#SET_ZONE PickupZoneSet The set of pickup zones which are used to determine from where the cargo can be picked up by the Ship. +-- @param Core.Set#SET_ZONE DeployZoneSet The set of deploy zones which determine where the cargo will be deployed by the Ship. +-- @param #table ShippingLane Table containing list of Shipping Lanes to be used +-- @return #AI_CARGO_DISPATCHER_SHIP +-- @usage +-- +-- -- An AI dispatcher object for a naval group, moving cargo from pickup zones to deploy zones via a predetermined Shipping Lane +-- +-- local SetCargoInfantry = SET_CARGO:New():FilterTypes( "Infantry" ):FilterStart() +-- local SetShip = SET_GROUP:New():FilterPrefixes( "Ship" ):FilterStart() +-- local SetPickupZones = SET_ZONE:New():FilterPrefixes( "Pickup" ):FilterStart() +-- local SetDeployZones = SET_ZONE:New():FilterPrefixes( "Deploy" ):FilterStart() +-- NEED MORE THOUGHT - ShippingLane is part of Warehouse....... +-- local ShippingLane = GROUP:New():FilterPrefixes( "ShippingLane" ):FilterStart() +-- +-- AICargoDispatcherShip = AI_CARGO_DISPATCHER_SHIP:New( SetShip, SetCargoInfantry, SetPickupZones, SetDeployZones, ShippingLane ) +-- AICargoDispatcherShip:Start() +-- +function AI_CARGO_DISPATCHER_SHIP:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet, ShippingLane ) + + local self = BASE:Inherit( self, AI_CARGO_DISPATCHER:New( ShipSet, CargoSet, PickupZoneSet, DeployZoneSet ) ) + + self:SetPickupSpeed( 60, 10 ) + self:SetDeploySpeed( 60, 10 ) + + self:SetPickupRadius( 500, 6000 ) + self:SetDeployRadius( 500, 6000 ) + + self:SetPickupHeight( 0, 0 ) + self:SetDeployHeight( 0, 0 ) + + self:SetShippingLane( ShippingLane ) + + self:SetMonitorTimeInterval( 600 ) + + return self +end + +function AI_CARGO_DISPATCHER_SHIP:SetShippingLane( ShippingLane ) + self.ShippingLane = ShippingLane + + return self + +end + +function AI_CARGO_DISPATCHER_SHIP:AICargo( Ship, CargoSet ) + + return AI_CARGO_SHIP:New( Ship, CargoSet, 0, self.ShippingLane ) +end \ No newline at end of file diff --git a/Moose Development/Moose/AI/AI_Cargo_Ship.lua b/Moose Development/Moose/AI/AI_Cargo_Ship.lua new file mode 100644 index 000000000..e28e52aa2 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Ship.lua @@ -0,0 +1,397 @@ +--- **AI** -- (R2.5.1) - Models the intelligent transportation of infantry and other cargo. +-- +-- === +-- +-- ### Author: **acrojason** (derived from AI_Cargo_APC by FlightControl) +-- +-- === +-- +-- @module AI.AI_Cargo_Ship +-- @image AI_Cargo_Dispatching_For_Ship.JPG + +--- @type AI_CARGO_SHIP +-- @extends AI.AI_Cargo#AI_CARGO + +--- Brings a dynamic cargo handling capability for an AI naval group. +-- +-- Naval ships can be utilized to transport cargo around the map following naval shipping lanes. +-- The AI_CARGO_SHIP class uses the @{Cargo.Cargo} capabilities within the MOOSE framework. +-- @{Cargo.Cargo} must be declared within the mission or warehouse to make the AI_CARGO_SHIP recognize the cargo. +-- Please consult the @{Cargo.Cargo} module for more information. +-- +-- ## Cargo loading. +-- +-- The module will automatically load cargo when the Ship is within boarding or loading radius. +-- The boarding or loading radius is specified when the cargo is created in the simulation and depends on the type of +-- cargo and the specified boarding radius. +-- +-- ## Defending the Ship when enemies are nearby +-- This is not supported for naval cargo because most tanks don't float. Protect your transports... +-- +-- ## Infantry or cargo **health**. +-- When cargo is unboarded from the Ship, the cargo is actually respawned into the battlefield. +-- As a result, the unboarding cargo 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 newly spawned units as a parameter. +-- However, cargo that was destroyed when unboarded and following the Ship won't be respawned again (this is likely not a thing for +-- naval cargo due to the lack of support for defending the Ship mentioned above). 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. Given the relatively short duration of DCS missions and the somewhat +-- lengthy naval transport times, most units entering the Ship as cargo will be freshly en route to an amphibious landing or transporting +-- between warehouses. +-- +-- ## Control the Ships on the map. +-- +-- Currently, naval transports can only be controlled via scripts due to their reliance upon predefined Shipping Lanes created in the Mission +-- Editor. An interesting future enhancement could leverage new pathfinding functionality for ships in the Ops module. +-- +-- ## Cargo deployment. +-- +-- Using the @{AI_CARGO_SHIP.Deploy}() method, you are able to direct the Ship towards a Deploy zone to unboard/unload the cargo at the +-- specified coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment. +-- +-- ## Cargo pickup. +-- +-- Using the @{AI_CARGO_SHIP.Pickup}() method, you are able to direct the Ship towards a Pickup zone to board/load the cargo at the specified +-- coordinate. The Ship will follow the Shipping Lane to ensure consistent cargo transportation within the simulation environment. +-- +-- +-- @field #AI_CARGO_SHIP +AI_CARGO_SHIP = { + ClassName = "AI_CARGO_SHIP", + Coordinate = nil -- Core.Point#COORDINATE +} + +--- Creates a new AI_CARGO_SHIP object. +-- @param #AI_CARGO_SHIP self +-- @param Wrapper.Group#GROUP Ship The carrier Ship group +-- @param Core.Set#SET_CARGO CargoSet The set of cargo to be transported +-- @param #number CombatRadius Provide the combat radius to defend the carrier by unboarding the cargo when enemies are nearby. When CombatRadius is 0, no defense will occur. +-- @param #table ShippingLane Table containing list of Shipping Lanes to be used +-- @return #AI_CARGO_SHIP +function AI_CARGO_SHIP:New( Ship, CargoSet, CombatRadius, ShippingLane ) + + local self = BASE:Inherit( self, AI_CARGO:New( Ship, CargoSet ) ) -- #AI_CARGO_SHIP + + self:AddTransition( "*", "Monitor", "*" ) + self:AddTransition( "*", "Destroyed", "Destroyed" ) + self:AddTransition( "*", "Home", "*" ) + + self:SetCombatRadius( 0 ) -- Don't want to deploy cargo in middle of water to defend Ship, so set CombatRadius to 0 + self:SetShippingLane ( ShippingLane ) + + self:SetCarrier( Ship ) + + return self +end + +--- Set the Carrier +-- @param #AI_CARGO_SHIP self +-- @param Wrapper.Group#GROUP CargoCarrier +-- @return #AI_CARGO_SHIP +function AI_CARGO_SHIP:SetCarrier( CargoCarrier ) + self.CargoCarrier = CargoCarrier -- Wrapper.Group#GROUIP + self.CargoCarrier:SetState( self.CargoCarrier, "AI_CARGO_SHIP", self ) + + CargoCarrier:HandleEvent( EVENTS.Dead ) + + function CargoCarrier:OnEventDead( EventData ) + self:F({"dead"}) + local AICargoTroops = self:GetState( self, "AI_CARGO_SHIP" ) + self:F({AICargoTroops=AICargoTroops}) + if AICargoTroops then + self:F({}) + if not AICargoTroops:Is( "Loaded" ) then + -- Better hope they can swim! + AICargoTroops:Destroyed() + end + end + end + + self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, self.CombatRadius ) + self.Coalition = self.CargoCarrier:GetCoalition() + + self:SetControllable( CargoCarrier ) + + return self +end + + +--- FInd a free Carrier within a radius +-- @param #AI_CARGO_SHIP self +-- @param Core.Point#COORDINATE Coordinate +-- @param #number Radius +-- @return Wrapper.Group#GROUP NewCarrier +function AI_CARGO_SHIP:FindCarrier( Coordinate, Radius ) + + local CoordinateZone = ZONE_RADIUS:New( "Zone", Coordinate:GetVec2(), Radius ) + CoordinateZone:Scan( { Object.Category.UNIT } ) + for _, DCSUnit in pairs( CoordinateZone:GetScannedUnits() ) do + local NearUnit = UNIT:Find( DCSUnit ) + self:F({NearUnit=NearUnit}) + if not NearUnit:GetState( NearUnit, "AI_CARGO_SHIP" ) then + local Attributes = NearUnit:GetDesc() + self:F({Desc=Attributes}) + if NearUnit:HasAttributes( "Trucks" ) then + return NearUnit:GetGroup() + end + end + end + + return nil +end + +function AI_CARGO_SHIP:SetShippingLane( ShippingLane ) + self.ShippingLane = ShippingLane + + return self +end + +function AI_CARGO_SHIP:SetCombatRadius( CombatRadius ) + self.CombatRadius = CombatRadius or 0 + + return self +end + + +--- Follow Infantry to the Carrier +-- @param #AI_CARGO_SHIP self +-- @param #AI_CARGO_SHIP Me +-- @param Wrapper.Unit#UNIT ShipUnit +-- @param Cargo.CargoGroup#CARGO_GROUP Cargo +-- @return #AI_CARGO_SHIP +function AI_CARGO_SHIP:FollowToCarrier( Me, ShipUnit, CargoGroup ) + + local InfantryGroup = CargoGroup:GetGroup() + + self:F( { self=self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) + + if ShipUnit:IsAlive() then + -- Check if the Cargo is near the CargoCarrier + if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", ShipUnit, 1000 ) ) then + + -- Cargo does not need to navigate to Carrier + Me:Guard() + else + + self:F( { InfantryGroup = InfantryGroup:GetName() } ) + if InfantryGroup:IsAlive() then + + self:F( { InfantryGroup = InfantryGroup:GetName() } ) + local Waypoints = {} + + -- Calculate new route + local FromCoord = InfantryGroup:GetCoordinate() + local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) + self:F({FromGround=FromGround}) + table.insert( Waypoints, FromGround ) + + local ToCoord = ShipUnit:GetCoordinate():GetRandomCoordinateInRadius( 10, 5 ) + local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) + self:F({ToGround=ToGround}) + table.insert( Waypoints, ToGround ) + + local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_SHIP.FollowToCarrier", Me, ShipUnit, CargoGroup ) + + self:F({Waypoints=Waypoints}) + local Waypoint = Waypoints[#Waypoints] + InfantryGroup:SetTaskWaypoint( Waypoint, TaskRoute ) -- Set for the given Route at Waypoint 2 the TaskRouteToZone + + InfantryGroup:Route( Waypoints, 1 ) -- Move after a random number of seconds to the Route. See Route method for details + end + end + end +end + + +function AI_CARGO_SHIP:onafterMonitor( Ship, From, Event, To ) + self:F( { Ship, From, Event, To, IsTransporting = self:IsTransporting() } ) + + if self.CombatRadius > 0 then + -- We really shouldn't find ourselves in here for Ships since the CombatRadius should always be 0. + -- This is to avoid Unloading the Ship in the middle of the sea. + if Ship and Ship:IsAlive() then + if self.CarrierCoordinate then + if self:IsTransporting() == true then + local Coordinate = Ship:GetCoordinate() + if self:Is( "Unloaded" ) or self:Is( "Loaded" ) then + self.Zone:Scan( { Object.Category.UNIT } ) + if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then + if self:Is( "Unloaded" ) then + -- There are no enemies within combat radius. Reload the CargoCarrier. + self:Reload() + end + else + if self:Is( "Loaded" ) then + -- There are enemies within combat radius. Unload the CargoCarrier. + self:__Unload( 1, nil, true ) -- The 2nd parameter is true, which means that the unload is for defending the carrier, not to deploy! + else + if self:Is( "Unloaded" ) then + --self:Follow() + end + self:F( "I am here" .. self:GetCurrentState() ) + if self:Is( "Following" ) then + for Cargo, ShipUnit in pairs( self.Carrier_Cargo ) do + local Cargo = Cargo -- Cargo.Cargo#CARGO + local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT + if Cargo:IsAlive() then + if not Cargo:IsNear( ShipUnit, 40 ) then + ShipUnit:RouteStop() + self.CarrierStopped = true + else + if self.CarrierStopped then + if Cargo:IsNear( ShipUnit, 25 ) then + ShipUnit:RouteResume() + self.CarrierStopped = nil + end + end + end + end + end + end + end + end + end + end + end + self.CarrierCoordinate = Ship:GetCoordinate() + end + self:__Monitor( -5 ) + end +end + +--- Check if cargo ship is alive and trigger Load event +-- @param Wrapper.Group#Group Ship +-- @param #AI_CARGO_SHIP self +function AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) + + Ship:F( { "AI_CARGO_Ship._Pickup:", Ship:GetName() } ) + + if Ship:IsAlive() then + self:Load( PickupZone ) + end +end + +--- Check if cargo ship is alive and trigger Unload event. Good time to remind people that Lua is case sensitive and Unload != UnLoad +-- @param Wrapper.Group#GROUP Ship +-- @param #AI_CARGO_SHIP self +function AI_CARGO_SHIP._Deploy( Ship, self, Coordinate, DeployZone ) + Ship:F( { "AI_CARGO_Ship._Deploy:", Ship } ) + + if Ship:IsAlive() then + self:Unload( DeployZone ) + end +end + +--- on after Pickup event. +-- @param AI_CARGO_SHIP Ship +-- @param From +-- @param Event +-- @param To +-- @param Core.Point#COORDINATE Coordinate of the pickup point +-- @param #number Speed Speed in km/h to sail to the pickup coordinate. Default is 50% of max speed for the unit +-- @param #number Height Altitude in meters to move to the pickup coordinate. This parameter is ignored for Ships +-- @param Core.Zone#ZONE PickupZone (optional) The zone where the cargo will be picked up. The PickupZone can be nil if there was no PickupZoneSet provided +function AI_CARGO_SHIP:onafterPickup( Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) + + if Ship and Ship:IsAlive() then + AI_CARGO_SHIP._Pickup( Ship, self, Coordinate, Speed, PickupZone ) + self:GetParent( self, AI_CARGO_SHIP ).onafterPickup( self, Ship, From, Event, To, Coordinate, Speed, Height, PickupZone ) + end +end + +--- On after Deploy event. +-- @param #AI_CARGO_SHIP self +-- @param Wrapper.Group#GROUP SHIP +-- @param From +-- @param Event +-- @param To +-- @param Core.Point#COORDINATE Coordinate Coordinate of the deploy point +-- @param #number Speed Speed in km/h to sail to the deploy coordinate. Default is 50% of max speed for the unit +-- @param #number Height Altitude in meters to move to the deploy coordinate. This parameter is ignored for Ships +-- @param Core.Zone#ZONE DeployZone The zone where the cargo will be deployed. +function AI_CARGO_SHIP:onafterDeploy( Ship, From, Event, To, Coordinate, Speed, Height, DeployZone ) + + if Ship and Ship:IsAlive() then + + Speed = Speed or Ship:GetSpeedMax()*0.8 + local lane = self.ShippingLane + + if lane then + local Waypoints = {} + + for i=1, #lane do + local coord = lane[i] + local Waypoint = coord:WaypointGround(_speed) + table.insert(Waypoints, Waypoint) + end + + local TaskFunction = Ship:TaskFunction( "AI_CARGO_SHIP._Deploy", self, Coordinate, DeployZone ) + local Waypoint = Waypoints[#Waypoints] + Ship:SetTaskWaypoint( Waypoint, TaskFunction ) + Ship:Route(Waypoints, 1) + self:GetParent( self, AI_CARGO_SHIP ).onafterDeploy( self, Ship, From, Event, To, Coordinate, Speed, Height, DeployZone ) + else + self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") + end + end +end + +--- On after Unload event. +-- @param #AI_CARGO_SHIP self +-- @param Wrapper.Group#GROUP Ship +-- @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_SHIP:onafterUnload( Ship, From, Event, To, DeployZone, Defend ) + self:F( { Ship, From, Event, To, DeployZone, Defend = Defend } ) + + local UnboardInterval = 5 + local UnboardDelay = 5 + + if Ship and Ship:IsAlive() then + for _, ShipUnit in pairs( Ship:GetUnits() ) do + local ShipUnit = ShipUnit -- Wrapper.Unit#UNIT + Ship:RouteStop() + for _, Cargo in pairs( ShipUnit:GetCargo() ) do + self:F( { Cargo = Cargo:GetName(), Isloaded = Cargo:IsLoaded() } ) + if Cargo:IsLoaded() then + local unboardCoord = DeployZone:GetRandomPointVec2() + Cargo:__UnBoard( UnboardDelay, unboardCoord, 1000) + UnboardDelay = UnboardDelay + Cargo:GetCount() * UnboardInterval + self:__Unboard( UnboardDelay, Cargo, ShipUnit, DeployZone, Defend ) + if not Defend == true then + Cargo:SetDeployed( true ) + end + end + end + end + end +end + +function AI_CARGO_SHIP:onafterHome( Ship, From, Event, To, Coordinate, Speed, Height, HomeZone ) + if Ship and Ship:IsAlive() then + + self.RouteHome = true + Speed = Speed or Ship:GetSpeedMax()*0.8 + local lane = self.ShippingLane + + if lane then + local Waypoints = {} + + -- Need to find a more generalized way to do this instead of reversing the shipping lane. + -- This only works if the Source/Dest route waypoints are numbered 1..n and not n..1 + for i=#lane, 1, -1 do + local coord = lane[i] + local Waypoint = coord:WaypointGround(_speed) + table.insert(Waypoints, Waypoint) + end + + local Waypoint = Waypoints[#Waypoints] + Ship:Route(Waypoints, 1) + + else + self:E(self.lid.."ERROR: No shipping lane defined for Naval Transport!") + end + end +end diff --git a/Moose Development/Moose/Cargo/CargoUnit.lua b/Moose Development/Moose/Cargo/CargoUnit.lua index 61319476d..88ba8c725 100644 --- a/Moose Development/Moose/Cargo/CargoUnit.lua +++ b/Moose Development/Moose/Cargo/CargoUnit.lua @@ -100,7 +100,12 @@ do -- CARGO_UNIT -- Respawn the group... if self.CargoObject then - self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading ) + if CargoCarrier:IsShip() then + -- If CargoCarrier is a ship, we don't want to spawn the units in the water next to the boat. Use destination coord instead. + self.CargoObject:ReSpawnAt( ToPointVec2, CargoDeployHeading ) + else + self.CargoObject:ReSpawnAt( FromPointVec2, CargoDeployHeading ) + end self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) self.CargoCarrier = nil diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index e8423265a..1c0366ea2 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1569,6 +1569,7 @@ WAREHOUSE = { delivered = {}, defending = {}, portzone = nil, + harborzone = nil, shippinglanes = {}, offroadpaths = {}, autodefence = false, @@ -1776,7 +1777,7 @@ WAREHOUSE.version="1.0.2" -- 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: Handle the case when units of a group die during the transfer. --- TODO: Added habours as interface for transport to from warehouses? Could make a rudimentary shipping dispatcher. +-- DONE: Added harbours as interface for transport to/from warehouses. Simplifies process of spawning units near the ship, especially if cargo not self-propelled. -- DONE: Test capturing a neutral warehouse. -- DONE: Add save/load capability of warehouse <==> persistance after mission restart. Difficult in lua! -- DONE: Get cargo bay and weight from CARGO_GROUP and GROUP. No necessary any more! @@ -1829,7 +1830,6 @@ WAREHOUSE.version="1.0.2" -- @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:T({warehouse=warehouse}) -- Check if just a string was given and convert to static. if type(warehouse)=="string" then @@ -2709,6 +2709,18 @@ function WAREHOUSE:SetPortZone(zone) return self end +--- Add a Harbor Zone for this warehouse where naval cargo units will spawn and be received. +-- Both warehouses must have the harbor zone defined for units to properly spawn on both the +-- sending and receiving side. The harbor zone should be within 3km of the port zone used for +-- warehouse in order to facilitate the boarding process. +-- @param #WAREHOUSE self +-- @param Core.Zone#ZONE zone The zone defining the naval embarcation/debarcation point for cargo units +-- @return #WAREHOUSE self +function WAREHOUSE:SetHarborZone(zone) + self.harborzone=zone + return self +end + --- 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 @@ -4342,8 +4354,8 @@ function WAREHOUSE:onafterRequest(From, Event, To, Request) elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then - self:_ErrorMessage("ERROR: Cargo transport by ship not supported yet!") - return + -- Spawn Ship in port zone + spawngroup=self:_SpawnAssetGroundNaval(_alias, _assetitem, Request, self.portzone) elseif Request.transporttype==WAREHOUSE.TransportType.SELFPROPELLED then @@ -4471,6 +4483,8 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet --_boardradius=nil elseif Request.transporttype==WAREHOUSE.TransportType.APC then --_boardradius=nil + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then + _boardradius=6000 end -- Empty cargo group set. @@ -4481,7 +4495,6 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Find asset belonging to this group. local asset=self:FindAssetInDB(_group) - -- New cargo group object. local cargogroup=CARGO_GROUP:New(_group, _cargotype,_group:GetName(),_boardradius, asset.loadradius) @@ -4490,6 +4503,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Add group to group set. CargoGroups:AddCargo(cargogroup) + end ------------------------ @@ -4535,23 +4549,52 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Set home zone. CargoTransport:SetHomeZone(self.spawnzone) + elseif Request.transporttype==WAREHOUSE.TransportType.SHIP then + + -- Pickup and deploy zones. + local PickupZoneSet = SET_ZONE:New():AddZone(self.portzone) + PickupZoneSet:AddZone(self.harborzone) + local DeployZoneSet = SET_ZONE:New():AddZone(Request.warehouse.harborzone) + + + -- Get the shipping lane to use and pass it to the Dispatcher + local remotename = Request.warehouse.warehouse:GetName() + local ShippingLane = self.shippinglanes[remotename][math.random(#self.shippinglanes[remotename])] + + -- Define dispatcher for this task. + CargoTransport = AI_CARGO_DISPATCHER_SHIP:New(TransportGroupSet, CargoGroups, PickupZoneSet, DeployZoneSet, ShippingLane) + + -- Set home zone + CargoTransport:SetHomeZone(self.portzone) + else self:E(self.lid.."ERROR: Unknown transporttype!") end -- Set pickup and deploy radii. -- 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. - local pickupouter=200 - local pickupinner=0 - if self.spawnzone.Radius~=nil then - pickupouter=self.spawnzone.Radius + local pickupouter = 200 + local pickupinner = 0 + local deployouter = 200 + local deployinner = 0 + if Request.transporttype==WAREHOUSE.TransportType.SHIP then + pickupouter=1000 pickupinner=20 - end - local deployouter=200 - local deployinner=0 - if self.spawnzone.Radius~=nil then - deployouter=Request.warehouse.spawnzone.Radius - deployinner=20 + deployouter=1000 + deployinner=0 + else + pickupouter=200 + pickupinner=0 + if self.spawnzone.Radius~=nil then + pickupouter=self.spawnzone.Radius + pickupinner=20 + end + deployouter=200 + deployinner=0 + if self.spawnzone.Radius~=nil then + deployouter=Request.warehouse.spawnzone.Radius + deployinner=20 + end end CargoTransport:SetPickupRadius(pickupouter, pickupinner) CargoTransport:SetDeployRadius(deployouter, deployinner) @@ -4630,7 +4673,7 @@ function WAREHOUSE:onafterRequestSpawned(From, Event, To, Request, CargoGroupSet -- Get cargo group object. local group=Cargo:GetObject() --Wrapper.Group#GROUP - -- Get request. + -- Get request. local request=warehouse:_GetRequestOfGroup(group, warehouse.pending) -- Add cargo group to this carrier. @@ -6928,8 +6971,12 @@ function WAREHOUSE:_CheckRequestValid(request) elseif request.transporttype==WAREHOUSE.TransportType.SHIP then -- Transport by ship. - self:E("ERROR: Incorrect request. Transport by SHIP not implemented 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 elseif request.transporttype==WAREHOUSE.TransportType.TRAIN then @@ -8052,7 +8099,7 @@ function WAREHOUSE:_GetAttribute(group) -- Ships local aircraftcarrier=group:HasAttribute("Aircraft Carriers") local warship=group:HasAttribute("Heavy armed ships") - local armedship=group:HasAttribute("Armed ships") + local armedship=group:HasAttribute("Armed Ship") local unarmedship=group:HasAttribute("Unarmed ships") diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index dde648962..c26eb8c33 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -99,10 +99,12 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Airplane.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Ship.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_APC.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Helicopter.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Airplane.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Dispatcher_Ship.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Assign.lua' ) __Moose.Include( 'Scripts/Moose/Actions/Act_Route.lua' ) diff --git a/Moose Development/Moose/Wrapper/Positionable.lua b/Moose Development/Moose/Wrapper/Positionable.lua index 00f828cb7..142f97128 100644 --- a/Moose Development/Moose/Wrapper/Positionable.lua +++ b/Moose Development/Moose/Wrapper/Positionable.lua @@ -1452,6 +1452,19 @@ do -- Cargo } self.__.CargoBayWeightLimit = Weights[Desc.typeName] or ( Desc.massMax - ( Desc.massEmpty + Desc.fuelMassMax ) ) + elseif self:IsShip() then + local Desc = self:GetDesc() + self:F({Desc=Desc}) + + local Weights = { + ["Type_071"] = 245000, + ["LHA_Tarawa"] = 500000, + ["Ropucha-class"] = 150000, + ["Dry-cargo ship-1"] = 70000, + ["Dry-cargo ship-2"] = 70000, + } + self.__.CargoBayWeightLimit = Weights[Desc.typeName] + else local Desc = self:GetDesc() diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 15de1d50c..b5a4d5031 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -1,3 +1,4 @@ +Utilities/Enums.lua Utilities/Routines.lua Utilities/Utils.lua Utilities/Enums.lua @@ -13,6 +14,7 @@ Core/Event.lua Core/Settings.lua Core/Menu.lua Core/Zone.lua +Core/Zone_Detection.lua Core/Database.lua Core/Set.lua Core/Point.lua @@ -20,6 +22,8 @@ Core/Velocity.lua Core/Message.lua Core/Fsm.lua Core/Radio.lua +Core/RadioQueue.lua +Core/RadioSpeech.lua Core/Spawn.lua Core/SpawnStatic.lua Core/Timer.lua @@ -52,6 +56,7 @@ Functional/Escort.lua Functional/MissileTrainer.lua Functional/ATC_Ground.lua Functional/Detection.lua +Functional/DetectionZones.lua Functional/Designate.lua Functional/RAT.lua Functional/Range.lua @@ -71,31 +76,35 @@ Ops/ATIS.lua AI/AI_Balancer.lua AI/AI_Air.lua -AI/AI_A2A.lua +AI/AI_Air_Patrol.lua +AI/AI_Air_Engage.lua AI/AI_A2A_Patrol.lua AI/AI_A2A_Cap.lua AI/AI_A2A_Gci.lua AI/AI_A2A_Dispatcher.lua -AI/AI_A2G.lua -AI/AI_A2G_Engage.lua AI/AI_A2G_BAI.lua AI/AI_A2G_CAS.lua AI/AI_A2G_SEAD.lua -AI/AI_A2G_Patrol.lua AI/AI_A2G_Dispatcher.lua AI/AI_Patrol.lua -AI/AI_Cap.lua -AI/AI_Cas.lua -AI/AI_Bai.lua +AI/AI_CAP.lua +AI/AI_CAS.lua +AI/AI_BAI.lua AI/AI_Formation.lua +AI/AI_Escort.lua +AI/AI_Escort_Request.lua +AI/AI_Escort_Dispatcher.lua +AI/AI_Escort_Dispatcher_Request.lua AI/AI_Cargo.lua AI/AI_Cargo_APC.lua AI/AI_Cargo_Helicopter.lua AI/AI_Cargo_Airplane.lua +AI/AI_Cargo_Ship.lua AI/AI_Cargo_Dispatcher.lua AI/AI_Cargo_Dispatcher_APC.lua AI/AI_Cargo_Dispatcher_Helicopter.lua AI/AI_Cargo_Dispatcher_Airplane.lua +AI/AI_Cargo_Dispatcher_Ship.lua Actions/Act_Assign.lua Actions/Act_Route.lua @@ -112,10 +121,11 @@ Tasking/Task_A2G_Dispatcher.lua Tasking/Task_A2G.lua Tasking/Task_A2A_Dispatcher.lua Tasking/Task_A2A.lua -Tasking/Task_Cargo.lua +Tasking/Task_CARGO.lua Tasking/Task_Cargo_Transport.lua Tasking/Task_Cargo_CSAR.lua Tasking/Task_Cargo_Dispatcher.lua -Tasking/TaskZoneCapture.lua +Tasking/Task_Capture_Zone.lua +Tasking/Task_Capture_Dispatcher.lua Globals.lua