diff --git a/Moose Development/Moose/AI/AI_Cargo_Troops.lua b/Moose Development/Moose/AI/AI_Cargo_Troops.lua new file mode 100644 index 000000000..defb6bd81 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Cargo_Troops.lua @@ -0,0 +1,247 @@ +--- **AI** -- (R2.3) - Models the intelligent transportation of infantry (cargo). +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI_Cargo_Troops + +--- @type AI_CARGO_TROOPS +-- @extends Core.Fsm#FSM_CONTROLLABLE + + +--- # AI\_CARGO\_TROOPS class, extends @{Core.Base@BASE} +-- +-- === +-- +-- @field #AI_CARGO_TROOPS +AI_CARGO_TROOPS = { + ClassName = "AI_CARGO_TROOPS", + Coordinate = nil -- Core.Point#COORDINATE, +} + +--- Creates a new AI_CARGO_TROOPS object. +-- @param #AI_CARGO_TROOPS self +-- @return #AI_CARGO_TROOPS +function AI_CARGO_TROOPS:New( CargoCarrier, CargoGroup, CombatRadius ) + + local self = BASE:Inherit( self, FSM_CONTROLLABLE:New( ) ) -- #AI_CARGO_TROOPS + + self.CargoCarrier = CargoCarrier -- Wrapper.Unit#UNIT + self.CargoGroup = CargoGroup -- Core.Cargo#CARGO_GROUP + self.CombatRadius = CombatRadius + + self.Zone = ZONE_UNIT:New( self.CargoCarrier:GetName() .. "-Zone", self.CargoCarrier, CombatRadius ) + self.Coalition = self.CargoCarrier:GetCoalition() + + self:SetControllable( CargoCarrier ) + + self:SetStartState( "UnLoaded" ) + + self:AddTransition( "*", "Load", "Boarding" ) + self:AddTransition( "Boarding", "Board", "Boarding" ) + self:AddTransition( "Boarding", "Loaded", "Loaded" ) + self:AddTransition( "Loaded", "Unload", "Unboarding" ) + self:AddTransition( "Unboarding", "Unboard", "Unboarding" ) + self:AddTransition( "Unboarding", "Unloaded", "Unloaded" ) + + self:AddTransition( "*", "Monitor", "*" ) + self:AddTransition( "*", "Follow", "Following" ) + self:AddTransition( "*", "Guard", "Guarding" ) + + self:__Monitor( 1 ) + self:__Load( 1 ) + + return self +end + + +--- Follow Infantry to the Carrier. +-- @param #AI_CARGO_TROOPS self +-- @param #AI_CARGO_TROOPS Me +-- @param Wrapper.Unit#UNIT CargoCarrier +-- @param Wrapper.Group#GROUP InfantryGroup +-- @return #AI_CARGO_TROOPS +function AI_CARGO_TROOPS:FollowToCarrier( Me, CargoCarrier, InfantryGroup ) + + self:F( { self = self:GetClassNameAndID(), InfantryGroup = InfantryGroup:GetName() } ) + + -- We check if the Cargo is near to the CargoCarrier. + if InfantryGroup:IsPartlyInZone( ZONE_UNIT:New( "Radius", CargoCarrier, 5 ) ) then + + -- The Cargo does not need to follow the Carrier. + Me:Guard() + + else + + self:F( { InfantryGroup = InfantryGroup:GetName() } ) + + if InfantryGroup:IsAlive() then + + self:F( { InfantryGroup = InfantryGroup:GetName() } ) + + local Waypoints = {} + + -- Calculate the new Route. + local FromCoord = InfantryGroup:GetCoordinate() + local FromGround = FromCoord:WaypointGround( 10, "Diamond" ) + self:F({FromGround=FromGround}) + table.insert( Waypoints, FromGround ) + + local ToCoord = CargoCarrier:GetCoordinate() + local ToGround = ToCoord:WaypointGround( 10, "Diamond" ) + self:F({ToGround=ToGround}) + table.insert( Waypoints, ToGround ) + + local TaskRoute = InfantryGroup:TaskFunction( "AI_CARGO_TROOPS.FollowToCarrier", Me, CargoCarrier, InfantryGroup ) + + 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 seconds to the Route. See the Route method for details. + end + end +end + + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterMonitor( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + if self.CarrierCoordinate then + local Coordinate = CargoCarrier:GetCoordinate() + self.Zone:Scan( { Object.Category.UNIT } ) + if self.Zone:IsAllInZoneOfCoalition( self.Coalition ) then + if self:Is( "Unloaded" ) or self:Is( "Guarding" ) or self:Is( "Following" ) then + -- There are no enemies within combat range. Load the CargoCarrier. + self:__Load( 1 ) + end + else + if self:Is( "Loaded" ) then + -- There are enemies within combat range. Unload the CargoCarrier. + self:__Unload( 1 ) + end + end + if self:Is( "Guarding" ) then + if not self.CargoGroup:IsNear( CargoCarrier, 5 ) then + self:Follow() + end + end + end + self.CarrierCoordinate = CargoCarrier:GetCoordinate() + end + + self:__Monitor( -5 ) + +end + + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterLoad( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + CargoCarrier:RouteStop() + self:Board() + self.CargoGroup:Board( CargoCarrier, 25 ) + end + +end + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterBoard( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + self:F({ IsLoaded = self.CargoGroup:IsLoaded() } ) + if not self.CargoGroup:IsLoaded() then + self:__Board( 1 ) + else + self:__Loaded( 1 ) + end + end + +end + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterLoaded( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + CargoCarrier:RouteResume() + end + +end + + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterUnload( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + CargoCarrier:RouteStop() + self.CargoGroup:UnBoard( ) + self:__Unboard( 1 ) + end + +end + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterUnboard( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + if not self.CargoGroup:IsUnLoaded() then + self:__Unboard( 1 ) + else + self:Unloaded() + end + end + +end + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterUnloaded( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + if CargoCarrier and CargoCarrier:IsAlive() then + self:Guard() + self.CargoCarrier = CargoCarrier + CargoCarrier:RouteResume() + end + +end + + +--- @param #AI_CARGO_TROOPS self +-- @param Wrapper.Unit#UNIT CargoCarrier +function AI_CARGO_TROOPS:onafterFollow( CargoCarrier, From, Event, To ) + self:F( { CargoCarrier, From, Event, To } ) + + self:F( "Follow" ) + if CargoCarrier and CargoCarrier:IsAlive() then + self.CargoGroup.CargoSet:ForEach( + --- @param Core.Cargo#CARGO Cargo + function( Cargo ) + self:F( { "Follow", Cargo.CargoObject:GetName() } ) + if Cargo.CargoObject:IsAlive() == true then + self:F( { "Follow", Cargo.CargoObject:GetID() } ) + self:FollowToCarrier( self, CargoCarrier, Cargo.CargoObject:GetGroup() ) + end + end + ) + end + +end + diff --git a/Moose Development/Moose/Core/Cargo.lua b/Moose Development/Moose/Core/Cargo.lua index 7a1949fbc..8cfa3037f 100644 --- a/Moose Development/Moose/Core/Cargo.lua +++ b/Moose Development/Moose/Core/Cargo.lua @@ -165,7 +165,7 @@ do -- CARGO -- @field #string Name A string defining the name of the cargo. The name is the unique identifier of the cargo. -- @field #number Weight A number defining the weight of the cargo. The weight is expressed in kg. -- @field #number NearRadius (optional) A number defining the radius in meters when the cargo is near to a Carrier, so that it can be loaded. - -- @field Wrapper.Controllable#CONTROLLABLE CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... + -- @field Wrapper.Unit#UNIT CargoObject The alive DCS object representing the cargo. This value can be nil, meaning, that the cargo is not represented anywhere... -- @field Wrapper.Client#CLIENT CargoCarrier The alive DCS object carrying the cargo. This value can be nil, meaning, that the cargo is not contained anywhere... -- @field #boolean Slingloadable This flag defines if the cargo can be slingloaded. -- @field #boolean Moveable This flag defines if the cargo is moveable. @@ -493,17 +493,22 @@ end -- @param #number NearRadius The radius when the cargo will board the Carrier (to avoid collision). -- @return #boolean function CARGO:IsNear( PointVec2, NearRadius ) - self:F( { PointVec2, NearRadius } ) + self:F( { PointVec2 = PointVec2, NearRadius = NearRadius } ) - --local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) - local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) - self:T( Distance ) - - if Distance <= NearRadius then - return true - else - return false + if self.CargoObject:IsAlive() then + --local Distance = PointVec2:DistanceFromPointVec2( self.CargoObject:GetPointVec2() ) + self:F( { CargoObjectName = self.CargoObject:GetName() } ) + self:F( { CargoObjectVec2 = self.CargoObject:GetVec2() } ) + self:F( { PointVec2 = PointVec2:GetVec2() } ) + local Distance = PointVec2:Get2DDistance( self.CargoObject:GetPointVec2() ) + self:T( Distance ) + + if Distance <= NearRadius then + return true + end end + + return false end --- Get the current PointVec2 of the cargo. @@ -744,7 +749,7 @@ do -- CARGO_UNIT function CARGO_UNIT:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:F( { From, Event, To, ToPointVec2, NearRadius } ) - NearRadius = NearRadius or 25 + NearRadius = NearRadius or 100 local Angle = 180 local Speed = 60 @@ -753,43 +758,45 @@ do -- CARGO_UNIT if From == "Loaded" then - local CargoCarrier = self.CargoCarrier -- Wrapper.Client#CLIENT + if not self:IsDestroyed() then - local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() - local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. - local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) - - - local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) - - - -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 + local CargoCarrier = self.CargoCarrier -- Wrapper.Controllable#CONTROLLABLE + + local CargoCarrierPointVec2 = CargoCarrier:GetPointVec2() + local CargoCarrierHeading = self.CargoCarrier:GetHeading() -- Get Heading of object in degrees. + local CargoDeployHeading = ( ( CargoCarrierHeading + Angle ) >= 360 ) and ( CargoCarrierHeading + Angle - 360 ) or ( CargoCarrierHeading + Angle ) + + + local CargoRoutePointVec2 = CargoCarrierPointVec2:Translate( RouteDistance, CargoDeployHeading ) + + + -- if there is no ToPointVec2 given, then use the CargoRoutePointVec2 ToPointVec2 = ToPointVec2 or CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, 5 ) - local DirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2) - local Angle = CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3) - + local DirectionVec3 = CargoCarrierPointVec2:GetDirectionVec3(ToPointVec2) + local Angle = CargoCarrierPointVec2:GetAngleDegrees(DirectionVec3) + local CargoDeployPointVec2 = CargoCarrierPointVec2:Translate( DeployDistance, Angle ) local CargoDeployPointVec2 = CargoCarrierPointVec2:GetRandomCoordinateInRadius( NearRadius, 5 ) - - local FromPointVec2 = CargoCarrierPointVec2 - - -- Respawn the group... - if self.CargoObject then - self:F( { CargoObject = self.CargoObject } ) - self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) - --self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) - self.CargoCarrier = nil - - local Points = {} - Points[#Points+1] = CargoCarrierPointVec2:WaypointGround( Speed ) - Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) + local FromPointVec2 = CargoCarrierPointVec2 - local TaskRoute = self.CargoObject:TaskRoute( Points ) - self.CargoObject:SetTask( TaskRoute, 1 ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius ) + -- Respawn the group... + if self.CargoObject then + self.CargoObject:ReSpawn( CargoDeployPointVec2:GetVec3(), CargoDeployHeading ) + self:F( { "CargoUnits:", self.CargoObject:GetGroup():GetName() } ) + self.CargoCarrier = nil + + local Points = {} + Points[#Points+1] = CargoCarrierPointVec2:WaypointGround( Speed ) + + Points[#Points+1] = ToPointVec2:WaypointGround( Speed ) + + local TaskRoute = self.CargoObject:TaskRoute( Points ) + self.CargoObject:SetTask( TaskRoute, 1 ) + + + self:__UnBoarding( 1, ToPointVec2, NearRadius ) + end end end @@ -804,7 +811,7 @@ do -- CARGO_UNIT function CARGO_UNIT:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:F( { From, Event, To, ToPointVec2, NearRadius } ) - NearRadius = NearRadius or 25 + NearRadius = NearRadius or 100 local Angle = 180 local Speed = 10 @@ -831,7 +838,7 @@ do -- CARGO_UNIT function CARGO_UNIT:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius ) self:F( { From, Event, To, ToPointVec2, NearRadius } ) - NearRadius = NearRadius or 25 + NearRadius = NearRadius or 100 self.CargoInAir = self.CargoObject:InAir() @@ -944,7 +951,7 @@ do -- CARGO_UNIT self:F( { From, Event, To, CargoCarrier.UnitName, NearRadius } ) - if CargoCarrier and CargoCarrier:IsAlive() then + if CargoCarrier and CargoCarrier:IsAlive() and self.CargoObject and self.CargoObject:IsAlive() then if CargoCarrier:InAir() == false then if self:IsNear( CargoCarrier:GetPointVec2(), NearRadius ) then self:__Load( 1, CargoCarrier, ... ) @@ -1180,6 +1187,7 @@ function CARGO_GROUP:New( CargoGroup, Type, Name, ReportRadius ) -- We create a new group object with one unit... -- First we prepare the template... GroupTemplate.name = GroupName .. "#CARGO#" .. UnitID + GroupTemplate.groupId = nil GroupTemplate.units = {} GroupTemplate.units[1] = UnitTemplate local UnitName = UnitTemplate.name .. "#CARGO" @@ -1223,252 +1231,251 @@ function CARGO_GROUP:OnEventCargoDead( EventData ) local Destroyed = false - if self:IsDestroyed() or self:IsUnLoaded() then - Destroyed = true - for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do - local Cargo = CargoData -- #CARGO - if Cargo:IsAlive() then - Destroyed = false - else - Cargo:Destroyed() - end - end - else - local CarrierName = self.CargoCarrier:GetName() - if CarrierName == EventData.IniDCSUnitName then - MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() + if self:IsDestroyed() or self:IsUnLoaded() or self:IsBoarding() then Destroyed = true - self.CargoCarrier:ClearCargo() + for CargoID, CargoData in pairs( self.CargoSet:GetSet() ) do + local Cargo = CargoData -- #CARGO + if Cargo:IsAlive() then + Destroyed = false + else + Cargo:Destroyed() + end + end + else + local CarrierName = self.CargoCarrier:GetName() + if CarrierName == EventData.IniDCSUnitName then + MESSAGE:New( "Cargo is lost from carrier " .. CarrierName, 15 ):ToAll() + Destroyed = true + self.CargoCarrier:ClearCargo() + end end - end - - if Destroyed then - self:Destroyed() - self:E( { "Cargo group destroyed" } ) - end - -end - ---- Enter Boarding State. --- @param #CARGO_GROUP self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local NearRadius = NearRadius or 25 - - if From == "UnLoaded" then - - -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo, ... ) - Cargo:__Board( 1, CargoCarrier, NearRadius, ... ) - end, ... - ) - self:__Boarding( 1, CargoCarrier, NearRadius, ... ) + if Destroyed then + self:Destroyed() + self:E( { "Cargo group destroyed" } ) + end + end - -end ---- Enter Loaded State. --- @param #CARGO_GROUP self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... ) - self:F( { From, Event, To, CargoCarrier, ...} ) + --- Enter Boarding State. + -- @param #CARGO_GROUP self + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + self:F( { CargoCarrier.UnitName, From, Event, To } ) + + local NearRadius = NearRadius or 25 + + if From == "UnLoaded" then - if From == "UnLoaded" then - -- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier. + -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo, ... ) + Cargo:__Board( 1, CargoCarrier, NearRadius, ... ) + end, ... + ) + + self:__Boarding( 1, CargoCarrier, NearRadius, ... ) + end + + end + + --- Enter Loaded State. + -- @param #CARGO_GROUP self + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterLoaded( From, Event, To, CargoCarrier, ... ) + self:F( { From, Event, To, CargoCarrier, ...} ) + + if From == "UnLoaded" then + -- For each Cargo object within the CARGO_GROUP, load each cargo to the CargoCarrier. + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + Cargo:Load( CargoCarrier ) + end + end + + --self.CargoObject:Destroy() + self.CargoCarrier = CargoCarrier + + end + + --- Leave Boarding State. + -- @param #CARGO_GROUP self + -- @param Wrapper.Unit#UNIT CargoCarrier + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) + self:F( { CargoCarrier.UnitName, From, Event, To } ) + + local NearRadius = NearRadius or 100 + + local Boarded = true + local Cancelled = false + local Dead = true + + self.CargoSet:Flush() + + -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - Cargo:Load( CargoCarrier ) - end - end + self:T( { Cargo:GetName(), Cargo.current } ) + + + if not Cargo:is( "Loaded" ) + and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function. + Boarded = false + end + + if Cargo:is( "UnLoaded" ) then + Cancelled = true + end - --self.CargoObject:Destroy() - self.CargoCarrier = CargoCarrier + if not Cargo:is( "Destroyed" ) then + Dead = false + end + + end -end - ---- Leave Boarding State. --- @param #CARGO_GROUP self --- @param Wrapper.Unit#UNIT CargoCarrier --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onafterBoarding( From, Event, To, CargoCarrier, NearRadius, ... ) - self:F( { CargoCarrier.UnitName, From, Event, To } ) - - local NearRadius = NearRadius or 25 - - local Boarded = true - local Cancelled = false - local Dead = true - - self.CargoSet:Flush() - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( { Cargo:GetName(), Cargo.current } ) - - - if not Cargo:is( "Loaded" ) - and (not Cargo:is( "Destroyed" )) then -- If one or more units of a group defined as CARGO_GROUP died, the CARGO_GROUP:Board() command does not trigger the CARGO_GRUOP:OnEnterLoaded() function. - Boarded = false - end - - if Cargo:is( "UnLoaded" ) then - Cancelled = true - end - - if not Cargo:is( "Destroyed" ) then - Dead = false - end - - end - - if not Dead then - - if not Cancelled then - if not Boarded then - self:__Boarding( 1, CargoCarrier, NearRadius, ... ) + if not Dead then + + if not Cancelled then + if not Boarded then + self:__Boarding( 1, CargoCarrier, NearRadius, ... ) + else + self:F("Group Cargo is loaded") + self:__Load( 1, CargoCarrier, ... ) + end else - self:__Load( 1, CargoCarrier, ... ) + self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... ) end else - self:__CancelBoarding( 1, CargoCarrier, NearRadius, ... ) + self:__Destroyed( 1, CargoCarrier, NearRadius, ... ) end - else - self:__Destroyed( 1, CargoCarrier, NearRadius, ... ) + end -end - ---- Get the amount of cargo units in the group. --- @param #CARGO_GROUP self --- @return #CARGO_GROUP -function CARGO_GROUP:GetCount() - return self.CargoSet:Count() -end - - ---- Enter UnBoarding State. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - self:F( {From, Event, To, ToPointVec2, NearRadius } ) - - NearRadius = NearRadius or 25 - - local CargoCarrier = self.CargoCarrier -- Wrapper.Client#CLIENT - - local Timer = 1 - - if From == "Loaded" then - - if self.CargoObject then - self.CargoObject:Destroy() - end - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo, NearRadius ) - - Cargo:__UnBoard( Timer, ToPointVec2, NearRadius ) - Timer = Timer + 1 - end, { NearRadius } - ) - - - self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) + --- Get the amount of cargo units in the group. + -- @param #CARGO_GROUP self + -- @return #CARGO_GROUP + function CARGO_GROUP:GetCount() + return self.CargoSet:Count() end -end ---- Leave UnBoarding State. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - --local NearRadius = NearRadius or 25 - - local Angle = 180 - local Speed = 10 - local Distance = 5 - - if From == "UnBoarding" then - local UnBoarded = true - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do - self:T( Cargo.current ) - if not Cargo:is( "UnLoaded" ) then - UnBoarded = false + --- Enter UnBoarding State. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) + self:F( {From, Event, To, ToPointVec2, NearRadius } ) + + NearRadius = NearRadius or 100 + + local Timer = 1 + + if From == "Loaded" then + + if self.CargoObject then + self.CargoObject:Destroy() end - end - if UnBoarded then - return true - else + -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo, NearRadius ) + + Cargo:__UnBoard( Timer, ToPointVec2, NearRadius ) + Timer = Timer + 10 + end, { NearRadius } + ) + + self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) end - - return false - end -end + end ---- UnBoard Event. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 ToPointVec2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) - self:F( { From, Event, To, ToPointVec2, NearRadius } ) - - --local NearRadius = NearRadius or 25 - - self:__UnLoad( 1, ToPointVec2, ... ) -end - - - ---- Enter UnLoaded State. --- @param #CARGO_GROUP self --- @param Core.Point#POINT_VEC2 --- @param #string Event --- @param #string From --- @param #string To -function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) - self:F( { From, Event, To, ToPointVec2 } ) - - if From == "Loaded" then - - -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 - self.CargoSet:ForEach( - function( Cargo ) - --Cargo:UnLoad( ToPointVec2 ) - local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10) - Cargo:UnLoad( RandomVec2 ) + --- Leave UnBoarding State. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onleaveUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) + + --local NearRadius = NearRadius or 25 + + local Angle = 180 + local Speed = 10 + local Distance = 5 + + if From == "UnBoarding" then + local UnBoarded = true + + -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 + for CargoID, Cargo in pairs( self.CargoSet:GetSet() ) do + self:T( Cargo.current ) + if not Cargo:is( "UnLoaded" ) and not Cargo:IsDestroyed() then + UnBoarded = false + end end - ) - + + if UnBoarded then + return true + else + self:__UnBoarding( 1, ToPointVec2, NearRadius, ... ) + end + + return false + end + end + + --- UnBoard Event. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 ToPointVec2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onafterUnBoarding( From, Event, To, ToPointVec2, NearRadius, ... ) + self:F( { From, Event, To, ToPointVec2, NearRadius } ) -end + --local NearRadius = NearRadius or 25 + + self:__UnLoad( 1, ToPointVec2, ... ) + end + + + + --- Enter UnLoaded State. + -- @param #CARGO_GROUP self + -- @param Core.Point#POINT_VEC2 + -- @param #string Event + -- @param #string From + -- @param #string To + function CARGO_GROUP:onenterUnLoaded( From, Event, To, ToPointVec2, ... ) + self:F( { From, Event, To, ToPointVec2 } ) + + if From == "Loaded" then + + -- For each Cargo object within the CARGO_GROUP, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + --Cargo:UnLoad( ToPointVec2 ) + local RandomVec2=ToPointVec2:GetRandomPointVec2InRadius(10) + Cargo:UnLoad( RandomVec2 ) + end + ) + + end + + end --- Respawn the cargo when destroyed @@ -1486,6 +1493,43 @@ end end end + + --- Route Cargo to Coordinate and randomize locations. + -- @param #CARGO_GROUP self + -- @param Core.Point#COORDINATE Coordinate + function CARGO_GROUP:RouteTo( Coordinate ) + self:F( {Coordinate = Coordinate } ) + + -- For each Cargo object within the CARGO_GROUPED, route each object to the CargoLoadPointVec2 + self.CargoSet:ForEach( + function( Cargo ) + Cargo.CargoObject:RouteGroundTo( Coordinate, 10, "vee", 0 ) + end + ) + + end + + --- Check if Cargo is near to the Carrier. + -- The Cargo is near to the Carrier if the first unit of the Cargo Group is within NearRadius. + -- @param #CARGO_GROUP self + -- @param Wrapper.Group#GROUP CargoCarrier + -- @param #number NearRadius + -- @return #boolean The Cargo is near to the Carrier. + -- @return #nil The Cargo is not near to the Carrier. + function CARGO_GROUP:IsNear( CargoCarrier, NearRadius ) + self:F( {NearRadius = NearRadius } ) + + local FirstCargo = self.CargoSet:GetFirst() -- #CARGO + + if FirstCargo then + if FirstCargo:IsNear( CargoCarrier:GetCoordinate(), NearRadius ) then + self:F( "Near" ) + return true + end + end + + return nil + end end -- CARGO_GROUP diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index f3eb0b5b7..0bb49c265 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -192,6 +192,20 @@ do -- COORDINATE return self end + --- COORDINATE constructor. + -- @param #COORDINATE self + -- @param #COORDINATE Coordinate. + -- @return #COORDINATE + function COORDINATE:NewFromCoordinate( Coordinate ) + + local self = BASE:Inherit( self, BASE:New() ) -- #COORDINATE + self.x = Coordinate.x + self.y = Coordinate.y + self.z = Coordinate.z + + return self + end + --- Create a new COORDINATE object from Vec2 coordinates. -- @param #COORDINATE self -- @param Dcs.DCSTypes#Vec2 Vec2 The Vec2 point. @@ -241,6 +255,25 @@ do -- COORDINATE return { x = self.x, y = self.z } end + + --- Returns if the 2 coordinates are at the same 2D position. + -- @param #COORDINATE self + -- @param #COORDINATE Coordinate + -- @param #number Precision + -- @return #boolean true if at the same position. + function COORDINATE:IsAtCoordinate2D( Coordinate, Precision ) + + self:F( { Coordinate = Coordinate:GetVec2() } ) + self:F( { self = self:GetVec2() } ) + + local x = Coordinate.x + local z = Coordinate.z + + return x - Precision <= self.x and x + Precision >= self.x and z - Precision <= self.z and z + Precision >= self.z + end + + + --TODO: check this to replace --- Calculate the distance from a reference @{DCSTypes#Vec2}. -- @param #COORDINATE self diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index b02e76168..939973fd1 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -152,13 +152,12 @@ function SET_BASE:Remove( ObjectName ) local Object = self.Set[ObjectName] - self:F3( { ObjectName, Object } ) - if Object then for Index, Key in ipairs( self.Index ) do if Key == ObjectName then table.remove( self.Index, Index ) self.Set[ObjectName] = nil + self:Flush(self) break end end @@ -3864,7 +3863,7 @@ function SET_AIRBASE:RemoveAirbasesByName( RemoveAirbaseNames ) local RemoveAirbaseNamesArray = ( type( RemoveAirbaseNames ) == "table" ) and RemoveAirbaseNames or { RemoveAirbaseNames } for RemoveAirbaseID, RemoveAirbaseName in pairs( RemoveAirbaseNamesArray ) do - self:Remove( RemoveAirbaseName.AirbaseName ) + self:Remove( RemoveAirbaseName ) end return self @@ -3926,12 +3925,41 @@ end function SET_AIRBASE:FilterStart() if _DATABASE then - self:_FilterStart() + + -- We use the BaseCaptured event, which is generated by DCS when a base got captured. + self:HandleEvent( EVENTS.BaseCaptured ) + + -- We initialize the first set. + for ObjectName, Object in pairs( self.Database ) do + if self:IsIncludeObject( Object ) then + self:Add( ObjectName, Object ) + else + self:RemoveAirbasesByName( ObjectName ) + end + end end return self end +--- Starts the filtering. +-- @param #SET_AIRBASE self +-- @param Core.Event#EVENT EventData +-- @return #SET_AIRBASE self +function SET_AIRBASE:OnEventBaseCaptured(EventData) + + -- When a base got captured, we reevaluate the set. + for ObjectName, Object in pairs( self.Database ) do + if self:IsIncludeObject( Object ) then + -- We add captured bases on yet in the set. + self:Add( ObjectName, Object ) + else + -- We remove captured bases that are not anymore part of the set. + self:RemoveAirbasesByName( ObjectName ) + end + end + +end --- Handles the Database to check on an event (birth) that the Object was added in the Database. -- This is required, because sometimes the _DATABASE birth event gets called later than the SET_BASE birth event! diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index fe3d1378d..01fe228e4 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1908,6 +1908,28 @@ function CONTROLLABLE:Route( Route, DelaySeconds ) end +--- Stops the movement of the vehicle on the route. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE +function CONTROLLABLE:RouteStop() + self:F2() + + local CommandStop = self:CommandStopRoute( true ) + self:SetCommand( CommandStop ) + +end + +--- Resumes the movement of the vehicle on the route. +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE +function CONTROLLABLE:RouteResume() + self:F2() + + local CommandResume = self:CommandStopRoute( false ) + self:SetCommand( CommandResume ) + +end + --- Make the GROUND Controllable to drive towards a specific point. -- @param #CONTROLLABLE self -- @param Core.Point#COORDINATE ToCoordinate A Coordinate to drive to. diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 695774052..9c0fcad57 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -224,6 +224,7 @@ function UNIT:ReSpawn( SpawnVec3, Heading ) for UnitTemplateID, UnitTemplateData in pairs( SpawnGroupTemplate.units ) do self:T( { UnitTemplateData.name, self:Name() } ) + SpawnGroupTemplate.units[UnitTemplateID].unitId = nil if UnitTemplateData.name == self:Name() then self:T("Adjusting") SpawnGroupTemplate.units[UnitTemplateID].alt = SpawnVec3.y @@ -264,6 +265,8 @@ function UNIT:ReSpawn( SpawnVec3, Heading ) end end + SpawnGroupTemplate.groupId = nil + self:T( SpawnGroupTemplate ) _DATABASE:Spawn( SpawnGroupTemplate ) @@ -383,7 +386,7 @@ function UNIT:GetGroup() local DCSUnit = self:GetDCSObject() if DCSUnit then - local UnitGroup = GROUP:Find( DCSUnit:getGroup() ) + local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() ) return UnitGroup end diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 1e575f0f2..49a8fc08b 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -61,6 +61,7 @@ AI/AI_Cap.lua AI/AI_Cas.lua AI/AI_Bai.lua AI/AI_Formation.lua +AI/AI_Cargo_Troops.lua Actions/Act_Assign.lua Actions/Act_Route.lua